wip
This commit is contained in:
@@ -94,7 +94,7 @@ export default function Home() {
|
|||||||
y={item.y}
|
y={item.y}
|
||||||
width={item.width}
|
width={item.width}
|
||||||
height={item.height}
|
height={item.height}
|
||||||
componentsId={item.componentsId}
|
widgetsId={item.widgetsId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</DraggablePanel>
|
</DraggablePanel>
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
import { useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import ComponentsStore from "@/stores/componentStore";
|
import ComponentsStore from "@/stores/componentStore";
|
||||||
import { componentsLibrary } from "./Draggable/Draggable";
|
import { useWidgets } from "@/hooks/useWidgets";
|
||||||
import { useDynamicWidgets } from "@/hooks/useDynamicWidgets";
|
|
||||||
|
|
||||||
export default function ComponentPaletteDrawer() {
|
export default function ComponentPaletteDrawer() {
|
||||||
const checkboxRef = useRef<HTMLInputElement>(null);
|
const checkboxRef = useRef<HTMLInputElement>(null);
|
||||||
const [componentsId, setComponentsId] = useState("logo");
|
const [widgetsId, setwidgetsId] = useState("logo");
|
||||||
const [data, setData] = useState("");
|
const [data, setData] = useState("");
|
||||||
const {widgets} = useDynamicWidgets();
|
const {widgets, widgetsLibrary} = useWidgets();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const widget = widgets.find(item => item.id === widgetsId);
|
||||||
|
const defaultConfig = widget?.defaultConfig({})
|
||||||
|
const stringData = JSON.stringify(defaultConfig, undefined, 4);
|
||||||
|
setData(stringData);
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}, [widgetsId]);
|
||||||
|
|
||||||
const onSubmit = (e: SubmitEvent) => {
|
const onSubmit = (e: SubmitEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ComponentsStore.addComponent(componentsId, data && JSON.parse(data));
|
ComponentsStore.addComponent(widgetsId, data && JSON.parse(data));
|
||||||
checkboxRef.current?.click();
|
checkboxRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="drawer drawer-end">
|
<div className="drawer drawer-end">
|
||||||
<input
|
<input
|
||||||
@@ -64,20 +75,15 @@ export default function ComponentPaletteDrawer() {
|
|||||||
|
|
||||||
<div className="resize-y border overflow-hidden max-w-full h-64 ">
|
<div className="resize-y border overflow-hidden max-w-full h-64 ">
|
||||||
{
|
{
|
||||||
componentsId && componentsLibrary[componentsId](data)
|
9 }
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
|
||||||
JSON.stringify(widgets)
|
|
||||||
}
|
|
||||||
|
|
||||||
<form onSubmit={onSubmit} className="w-full">
|
<form onSubmit={onSubmit} className="w-full">
|
||||||
<fieldset className="fieldset w-full">
|
<fieldset className="fieldset w-full">
|
||||||
<legend className="fieldset-legend">小部件</legend>
|
<legend className="fieldset-legend">小部件</legend>
|
||||||
<select
|
<select
|
||||||
value={componentsId}
|
value={widgetsId}
|
||||||
onChange={(e) => setComponentsId(e.target.value)}
|
onChange={(e) => setwidgetsId(e.target.value)}
|
||||||
className="select w-full"
|
className="select w-full"
|
||||||
>
|
>
|
||||||
<option disabled={true}>请选择小部件</option>
|
<option disabled={true}>请选择小部件</option>
|
||||||
@@ -93,6 +99,7 @@ export default function ComponentPaletteDrawer() {
|
|||||||
<fieldset className="fieldset w-full">
|
<fieldset className="fieldset w-full">
|
||||||
<legend className="fieldset-legend">配置信息</legend>
|
<legend className="fieldset-legend">配置信息</legend>
|
||||||
<textarea
|
<textarea
|
||||||
|
value={data}
|
||||||
onChange={(e) => setData(e.target.value)}
|
onChange={(e) => setData(e.target.value)}
|
||||||
className="textarea h-24 w-full"
|
className="textarea h-24 w-full"
|
||||||
placeholder="请输入 JSON 格式的配置信息"
|
placeholder="请输入 JSON 格式的配置信息"
|
||||||
|
|||||||
@@ -6,16 +6,17 @@ import { useDraggable } from "@dnd-kit/core";
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { nearestMultiple } from "./utils";
|
import { nearestMultiple } from "./utils";
|
||||||
import PreviewStore from "@/stores/previewStore";
|
import PreviewStore from "@/stores/previewStore";
|
||||||
import Logo from "../../widgets/Logo";
|
import { useWidgets } from "@/hooks/useWidgets";
|
||||||
import Text from "../../widgets/Text";
|
|
||||||
|
|
||||||
export default function Draggable(props: DraggablePropsType) {
|
export default function Draggable(props: DraggablePropsType) {
|
||||||
const targetRef = useRef<HTMLDivElement>(null);
|
const targetRef = useRef<HTMLDivElement>(null);
|
||||||
const timerRef = useRef<NodeJS.Timeout>(null);
|
const timerRef = useRef<NodeJS.Timeout>(null);
|
||||||
const [size, setSize] = useState({ width: 16, height: 16 });
|
const [size, setSize] = useState({ width: 16, height: 16 });
|
||||||
const { id, componentsId, data, x, y, width:_width, height:_height } = props;
|
const { id, widgetsId, data, x, y, width:_width, height:_height } = props;
|
||||||
const [width, setWidth] = useState(_width);
|
const [width, setWidth] = useState(_width);
|
||||||
const [height, setHeight] = useState(_height);
|
const [height, setHeight] = useState(_height);
|
||||||
|
const {widgets, widgetsLibrary} = useWidgets();
|
||||||
|
|
||||||
|
|
||||||
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
||||||
id,
|
id,
|
||||||
@@ -97,22 +98,17 @@ export default function Draggable(props: DraggablePropsType) {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{componentsLibrary[componentsId] && componentsLibrary[componentsId](data ?? {})}
|
{widgetsLibrary[widgetsId] && widgetsLibrary[widgetsId](data ?? {})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DraggablePropsType = {
|
export type DraggablePropsType = {
|
||||||
id: string;
|
id: string;
|
||||||
componentsId: string;
|
widgetsId: string;
|
||||||
data?: Record<string, unknown>;
|
data?: Record<string, unknown>;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const componentsLibrary = {
|
|
||||||
'logo': () => <Logo />,
|
|
||||||
'text': (data: DraggablePropsType["data"]) => <Text data={{ content: 'default',...data }} />,
|
|
||||||
}
|
|
||||||
@@ -43,7 +43,7 @@ export default function DraggablePanel(props: DraggablePanelType) {
|
|||||||
PreviewStore.clearPreview();
|
PreviewStore.clearPreview();
|
||||||
ComponentStore.changeComponent({
|
ComponentStore.changeComponent({
|
||||||
id: event?.active?.data?.current?.id,
|
id: event?.active?.data?.current?.id,
|
||||||
componentsId: event?.active?.data?.current?.componentsId,
|
widgetsId: event?.active?.data?.current?.widgetsId,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: nearestMultiple(width),
|
width: nearestMultiple(width),
|
||||||
|
|||||||
110
hooks/useWidgets.tsx
Normal file
110
hooks/useWidgets.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"use client";
|
||||||
|
import { useState, useCallback, useEffect, ReactNode } from 'react';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { DraggablePropsType } from '@/components/Draggable/Draggable';
|
||||||
|
import * as Logo from '@/widgets/Logo/index';
|
||||||
|
import * as Text from '@/widgets/Text/index';
|
||||||
|
import * as Image from '@/widgets/Image/index';
|
||||||
|
export interface WidgetModule {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
defaultConfig: (data: Record<string, unknown>) => Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type widgetsLibraryType = Record<string, (data?:DraggablePropsType['data']) => ReactNode>;
|
||||||
|
|
||||||
|
const widgetPaths = [
|
||||||
|
'@/widgets/Logo/index',
|
||||||
|
'@/widgets/Text/index',
|
||||||
|
'@/widgets/Image/index',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const useWidgets = (): {
|
||||||
|
widgets: WidgetModule[];
|
||||||
|
widgetsLibrary: widgetsLibraryType;
|
||||||
|
refreshWidgets: () => Promise<void>;
|
||||||
|
} => {
|
||||||
|
const [widgets, setWidgets] = useState<WidgetModule[]>([
|
||||||
|
{
|
||||||
|
id: 'logo',
|
||||||
|
name: 'Logo',
|
||||||
|
version: '1.0.0',
|
||||||
|
defaultConfig: Logo.defaultConfig
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'text',
|
||||||
|
name: '文本',
|
||||||
|
version: '1.0.0',
|
||||||
|
defaultConfig: Text.defaultConfig
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'image',
|
||||||
|
name: '图片',
|
||||||
|
version: '1.0.0',
|
||||||
|
defaultConfig: Image.defaultConfig
|
||||||
|
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const [widgetsLibrary, setWidgetsLibrary] = useState<widgetsLibraryType>({
|
||||||
|
'logo': (data) => {
|
||||||
|
const { default:Component, defaultConfig } = Logo;
|
||||||
|
|
||||||
|
return <Component {...defaultConfig(data)} />
|
||||||
|
},
|
||||||
|
'text': (data) => {
|
||||||
|
const { default:Component, defaultConfig } = Text;
|
||||||
|
return <Component data={defaultConfig(data)} />
|
||||||
|
},
|
||||||
|
'image': (data) => {
|
||||||
|
const { default:Component, defaultConfig } = Image;
|
||||||
|
return <Component data={defaultConfig(data)} />
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadWidgets = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
// Webpack 的 require.context 匹配 widgets 目录
|
||||||
|
console.log(window);
|
||||||
|
// const modules = await Promise.all(
|
||||||
|
// widgetPaths.map(async (key) => {
|
||||||
|
// console.log(key);
|
||||||
|
// debugger
|
||||||
|
// const meta = await require(key);
|
||||||
|
|
||||||
|
|
||||||
|
// // 创建动态组件(单独导入确保 Tree Shaking)
|
||||||
|
// // const Component = dynamic(
|
||||||
|
// // () => import(key),
|
||||||
|
// // { ssr: false }
|
||||||
|
// // );
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// id: meta.id,
|
||||||
|
// name: meta.name,
|
||||||
|
// version: meta.version,
|
||||||
|
// // component: Component
|
||||||
|
// };
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// console.log(modules);
|
||||||
|
|
||||||
|
// setWidgets(modules);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load widgets:', error);
|
||||||
|
setWidgets([]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 初始化加载
|
||||||
|
// useEffect(() => {
|
||||||
|
// loadWidgets();
|
||||||
|
// }, [loadWidgets]);
|
||||||
|
|
||||||
|
// 暴露刷新方法
|
||||||
|
const refreshWidgets = useCallback(async () => {
|
||||||
|
await loadWidgets();
|
||||||
|
}, [loadWidgets]);
|
||||||
|
|
||||||
|
return { widgets, widgetsLibrary, refreshWidgets };
|
||||||
|
};
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4.0.14",
|
"@tailwindcss/postcss": "^4.0.14",
|
||||||
|
"@types/lodash": "^4.17.16",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -45,6 +45,9 @@ importers:
|
|||||||
'@tailwindcss/postcss':
|
'@tailwindcss/postcss':
|
||||||
specifier: ^4.0.14
|
specifier: ^4.0.14
|
||||||
version: 4.0.14
|
version: 4.0.14
|
||||||
|
'@types/lodash':
|
||||||
|
specifier: ^4.17.16
|
||||||
|
version: 4.17.16
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20
|
specifier: ^20
|
||||||
version: 20.17.24
|
version: 20.17.24
|
||||||
@@ -468,6 +471,9 @@ packages:
|
|||||||
'@types/json5@0.0.29':
|
'@types/json5@0.0.29':
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
|
|
||||||
|
'@types/lodash@4.17.16':
|
||||||
|
resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==}
|
||||||
|
|
||||||
'@types/node@20.17.24':
|
'@types/node@20.17.24':
|
||||||
resolution: {integrity: sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==}
|
resolution: {integrity: sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==}
|
||||||
|
|
||||||
@@ -2181,6 +2187,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
|
|
||||||
|
'@types/lodash@4.17.16': {}
|
||||||
|
|
||||||
'@types/node@20.17.24':
|
'@types/node@20.17.24':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.19.8
|
undici-types: 6.19.8
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ class ComponentsStore {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addComponent(componentsId: string, data:Record<string, unknown>) {
|
addComponent(widgetsId: string, data:Record<string, unknown>) {
|
||||||
this.components.push({
|
this.components.push({
|
||||||
id: String(this.components.length),
|
id: String(this.components.length),
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
componentsId,
|
widgetsId,
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
37
widgets/Image/index.tsx
Normal file
37
widgets/Image/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"use client";
|
||||||
|
import "@/app/globals.css";
|
||||||
|
import { DraggablePropsType } from "@/components/Draggable/Draggable";
|
||||||
|
// import Image from "next/Image";
|
||||||
|
import { CSSProperties } from "react";
|
||||||
|
|
||||||
|
export const id = "image";
|
||||||
|
export const name = "Image";
|
||||||
|
export const version = "1.0.0";
|
||||||
|
export const defaultConfig = (config: ImageData) => {
|
||||||
|
return {
|
||||||
|
src: 'https://www.todaybing.com/api/today',
|
||||||
|
...config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageData = {
|
||||||
|
src: string;
|
||||||
|
customStyles?: CSSProperties; // 自定义CSS扩展
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ResponsiveImageProps {
|
||||||
|
data: DraggablePropsType["data"] & ImageData;
|
||||||
|
className?: string; // 外部容器类名
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const WidgetsImage: React.FC<ResponsiveImageProps> = ({
|
||||||
|
data,
|
||||||
|
className
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<img className="w-full h-full object-cover" src={data.src} style={data.customStyles} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WidgetsImage;
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
|
import { DraggablePropsType } from "@/components/Draggable/Draggable";
|
||||||
|
|
||||||
export const id = "logo";
|
export const id = "logo";
|
||||||
export const name = "Logo";
|
export const name = "Logo";
|
||||||
export const version = "1.0.0";
|
export const version = "1.0.0";
|
||||||
export default function Logo() {
|
export const defaultConfig = (config: DraggablePropsType['data']) => {
|
||||||
|
return {
|
||||||
|
...config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default function WidgetsLogo() {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center text-5xl bg-base-200 font-bold">
|
<div className="flex h-full w-full items-center justify-center text-5xl bg-base-200 font-bold">
|
||||||
<span className="whitespace-nowrap text-primary">
|
<span className="whitespace-nowrap text-primary">
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ import { DraggablePropsType } from '../../components/Draggable/Draggable';
|
|||||||
export const id = "text";
|
export const id = "text";
|
||||||
export const name = "文字";
|
export const name = "文字";
|
||||||
export const version = "1.0.0";
|
export const version = "1.0.0";
|
||||||
|
export const defaultConfig = (config: FontData) => {
|
||||||
|
return {
|
||||||
|
textAlign: 'center',
|
||||||
|
content: '默认文本',
|
||||||
|
...config,
|
||||||
|
}
|
||||||
|
}
|
||||||
type FontData = {
|
type FontData = {
|
||||||
content: string;
|
content: string;
|
||||||
fontSize?: number; // 字体大小(px)
|
fontSize?: number; // 字体大小(px)
|
||||||
@@ -21,7 +28,7 @@ interface ResponsiveTextProps {
|
|||||||
className?: string; // 外部容器类名
|
className?: string; // 外部容器类名
|
||||||
}
|
}
|
||||||
|
|
||||||
const Text: React.FC<ResponsiveTextProps> = ({
|
const WidgetsText: React.FC<ResponsiveTextProps> = ({
|
||||||
data,
|
data,
|
||||||
className
|
className
|
||||||
}) => {
|
}) => {
|
||||||
@@ -76,4 +83,4 @@ const Text: React.FC<ResponsiveTextProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Text;
|
export default WidgetsText;
|
||||||
|
|||||||
Reference in New Issue
Block a user