wip
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import DraggablePanel from "@/components/DraggablePanel";
|
import DraggablePanel from "@/components/DraggablePanel";
|
||||||
import Logo from "@/components/Logo";
|
import Logo from "@/widgets/Logo";
|
||||||
import Preview from "@/components/Draggable/Preview";
|
import Preview from "@/components/Draggable/Preview";
|
||||||
import Draggable from "@/components/Draggable/Draggable";
|
import Draggable from "@/components/Draggable/Draggable";
|
||||||
import PreviewStore from "@/stores/previewStore";
|
import PreviewStore from "@/stores/previewStore";
|
||||||
@@ -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}
|
||||||
component={() => <Logo />}
|
componentsId={item.componentsId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</DraggablePanel>
|
</DraggablePanel>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
import Logo from "@/components/Logo";
|
import Logo from "@/widgets/Logo";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
import { useRef } from "react";
|
import { useRef, useState } from "react";
|
||||||
|
import ComponentsStore from "@/stores/componentStore";
|
||||||
|
import { componentsLibrary } from "./Draggable/Draggable";
|
||||||
|
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 [data, setData] = useState("");
|
||||||
|
const {widgets} = useDynamicWidgets();
|
||||||
|
|
||||||
const onSubmit = (e: SubmitEvent) => {
|
const onSubmit = (e: SubmitEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
ComponentsStore.addComponent(componentsId, data && JSON.parse(data));
|
||||||
checkboxRef.current?.click();
|
checkboxRef.current?.click();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
@@ -25,7 +33,7 @@ export default function ComponentPaletteDrawer() {
|
|||||||
Add Component
|
Add Component
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="drawer-side">
|
<div className="drawer-side z-50">
|
||||||
<label
|
<label
|
||||||
htmlFor="ComponentPaletteDrawer"
|
htmlFor="ComponentPaletteDrawer"
|
||||||
aria-label="close sidebar"
|
aria-label="close sidebar"
|
||||||
@@ -37,7 +45,7 @@ export default function ComponentPaletteDrawer() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div role="alert" className="alert alert-warning">
|
<div role="alert" className="alert alert-warning mb-4">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
className="h-6 w-6 shrink-0 stroke-current"
|
className="h-6 w-6 shrink-0 stroke-current"
|
||||||
@@ -53,13 +61,31 @@ export default function ComponentPaletteDrawer() {
|
|||||||
</svg>
|
</svg>
|
||||||
<span>Warning: 内容处于开发阶段仅供参考!</span>
|
<span>Warning: 内容处于开发阶段仅供参考!</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="resize-y border overflow-hidden max-w-full h-64 ">
|
||||||
|
{
|
||||||
|
componentsId && componentsLibrary[componentsId](data)
|
||||||
|
}
|
||||||
|
</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 defaultValue="请选择小部件" className="select w-full">
|
<select
|
||||||
|
value={componentsId}
|
||||||
|
onChange={(e) => setComponentsId(e.target.value)}
|
||||||
|
className="select w-full"
|
||||||
|
>
|
||||||
<option disabled={true}>请选择小部件</option>
|
<option disabled={true}>请选择小部件</option>
|
||||||
<option>文本</option>
|
{
|
||||||
<option>Logo</option>
|
widgets.map(item => {
|
||||||
|
return (<option value={item.id}>{item.name}</option>)
|
||||||
|
})
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
<p className="fieldset-label">你可以选择一款你喜欢的小部件</p>
|
<p className="fieldset-label">你可以选择一款你喜欢的小部件</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@@ -67,9 +93,11 @@ 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
|
||||||
|
onChange={(e) => setData(e.target.value)}
|
||||||
className="textarea h-24 w-full"
|
className="textarea h-24 w-full"
|
||||||
placeholder="请输入 JSON 格式的配置信息"
|
placeholder="请输入 JSON 格式的配置信息"
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
||||||
<p className="fieldset-label">JSON格式的配置信息</p>
|
<p className="fieldset-label">JSON格式的配置信息</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<button className="btn w-full btn-primary">确认</button>
|
<button className="btn w-full btn-primary">确认</button>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ 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 "../Logo";
|
import Logo from "../../widgets/Logo";
|
||||||
import Text from "../Text";
|
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);
|
||||||
@@ -78,7 +78,7 @@ export default function Draggable(props: DraggablePropsType) {
|
|||||||
...style,
|
...style,
|
||||||
}} {...attributes}>
|
}} {...attributes}>
|
||||||
<button
|
<button
|
||||||
className="btn absolute top-1 right-1 z-50 btn-square btn-soft"
|
className="btn absolute top-1 right-1 z-40 btn-square btn-soft"
|
||||||
{...listeners}
|
{...listeners}
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ export default function Preview(props: PreviewPropsType) {
|
|||||||
<span
|
<span
|
||||||
className="absolute text-2xl bg-emerald-500 border-emerald-500 text-white top-0 p-0.5"
|
className="absolute text-2xl bg-emerald-500 border-emerald-500 text-white top-0 p-0.5"
|
||||||
>
|
>
|
||||||
{width * height === 0 || `${width} * ${height}`}
|
{width + height === 0 || `${width} * ${height}`}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="absolute text-2xl bg-emerald-500 border-emerald-500 text-white bottom-0 p-0.5"
|
className="absolute text-2xl bg-emerald-500 border-emerald-500 text-white bottom-0 p-0.5"
|
||||||
>
|
>
|
||||||
{x * y === 0 || `${x}, ${y}`}
|
{x + y === 0 || `${x}, ${y}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
* @param [x=16] 推荐使用偶数
|
* @param [x=16] 推荐使用偶数
|
||||||
* @returns 最近的 x 的倍数
|
* @returns 最近的 x 的倍数
|
||||||
*/
|
*/
|
||||||
export function nearestMultiple(n: number, x: number = 32): number {
|
export function nearestMultiple(n: number, x: number = 18): number {
|
||||||
return Math.floor((n + x/2) / x) * x;
|
return Math.floor((n + x/2) / x) * x;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
component: event?.active?.data?.current?.component,
|
componentsId: event?.active?.data?.current?.componentsId,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: nearestMultiple(width),
|
width: nearestMultiple(width),
|
||||||
|
|||||||
68
hooks/useDynamicWidgets.ts
Normal file
68
hooks/useDynamicWidgets.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
export interface WidgetModule {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
component: ReturnType<typeof dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDynamicWidgets = (): {
|
||||||
|
widgets: WidgetModule[];
|
||||||
|
refreshWidgets: () => Promise<void>;
|
||||||
|
} => {
|
||||||
|
const [widgets, setWidgets] = useState<WidgetModule[]>([]);
|
||||||
|
|
||||||
|
const loadWidgets = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
// Webpack 的 require.context 匹配 widgets 目录
|
||||||
|
const context = require.context('../widgets', true, /\/index\.tsx$/);
|
||||||
|
console.log("[DEBUG] Matched files:", context.keys());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const modules = await Promise.all(
|
||||||
|
context.keys().map(async (key) => {
|
||||||
|
const folderName = key.split('/')[1];
|
||||||
|
|
||||||
|
// 动态导入元数据
|
||||||
|
console.log("[DEBUG] import files:", `../widgets/${folderName}/index.tsx`);
|
||||||
|
|
||||||
|
const meta = await import(`../widgets/${folderName}/index.tsx`);
|
||||||
|
|
||||||
|
// 创建动态组件(单独导入确保 Tree Shaking)
|
||||||
|
const Component = dynamic(
|
||||||
|
() => import(`../widgets/${folderName}/index.tsx`),
|
||||||
|
{ 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, refreshWidgets };
|
||||||
|
};
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
// next.config.js
|
||||||
/* config options here */
|
const nextConfig:NextConfig = {
|
||||||
|
// 移除自定义 webpack 配置,Next.js 已内置 TypeScript 支持
|
||||||
|
// 保留以下配置即可
|
||||||
|
experimental: {
|
||||||
|
externalDir: true, // 如果需要引用外部目录
|
||||||
|
},
|
||||||
|
webpack: (config) => {
|
||||||
|
// 保留其他必要配置
|
||||||
|
return config;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
15
widgets/Logo/index copy.tsx
Normal file
15
widgets/Logo/index copy.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"use client";
|
||||||
|
import "@/app/globals.css";
|
||||||
|
|
||||||
|
export const id = "logo";
|
||||||
|
export const name = "Logo";
|
||||||
|
export const version = "1.0.0";
|
||||||
|
export default function Logo() {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center justify-center text-5xl bg-base-200 font-bold">
|
||||||
|
<span className="whitespace-nowrap text-primary">
|
||||||
|
NEXUSHUB
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import "@/app/globals.css";
|
import "@/app/globals.css";
|
||||||
|
|
||||||
|
export const id = "logo";
|
||||||
|
export const name = "Logo";
|
||||||
|
export const version = "1.0.0";
|
||||||
export default function Logo() {
|
export default function Logo() {
|
||||||
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">
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import React, { CSSProperties } from 'react';
|
import React, { CSSProperties } from 'react';
|
||||||
import { DraggablePropsType } from '../Draggable/Draggable';
|
import { DraggablePropsType } from '../../components/Draggable/Draggable';
|
||||||
|
|
||||||
|
export const id = "text";
|
||||||
|
export const name = "文字";
|
||||||
|
export const version = "1.0.0";
|
||||||
type FontData = {
|
type FontData = {
|
||||||
content: string;
|
content: string;
|
||||||
fontSize?: number; // 字体大小(px)
|
fontSize?: number; // 字体大小(px)
|
||||||
Reference in New Issue
Block a user