prasi-bun/app/web/src/nova/ed/panel/side/prop-master.tsx

455 lines
15 KiB
TypeScript

import {
Tree as DNDTree,
DndProvider,
NodeModel,
PlaceholderRender,
getBackendOptions,
} from "@minoru/react-dnd-treeview";
import { FC, useState } from "react";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useGlobal, useLocal } from "web-utils";
import { IItem } from "../../../../utils/types/item";
import { FMCompDef } from "../../../../utils/types/meta-fn";
import { EDGlobal, IMeta, active } from "../../logic/ed-global";
import { treeRebuild } from "../../logic/tree/build";
import { propPopover } from "./prop-master/prop-form";
import { EdPropCompTreeItem, PropItem } from "./prop-master/tree-item";
import { Popover } from "../../../../utils/ui/popover";
import { SimpleMonaco } from "./simple-monaco";
const propRef = {
el: null as any,
};
export const EdSidePropComp: FC<{ meta: IMeta }> = ({ meta }) => {
const p = useGlobal(EDGlobal, "EDITOR");
const item = meta?.item as IItem;
const local = useLocal({
json: "",
typings: "",
openJSON: false,
openTypings: false,
});
const [_, set] = useState({});
const render = () => {
set({});
};
propPopover.render = render;
if (!item) return null;
const TypedTree = DNDTree<PropItem>;
let filtered = [] as NodeModel<PropItem>[];
let mprops = meta.mitem?.get("component")?.get("props");
const mcomp = meta.mitem?.get("component");
if (!mprops) {
if (mcomp) {
mcomp.set("props", new Y.Map() as any);
mprops = mcomp.get("props");
}
}
if (mprops && meta.mitem) {
mprops.forEach((m, key) => {
filtered.push({
id: `${item.id}-${key}`,
parent: "root",
text: key,
droppable: true,
data: {
mitem: meta.mitem as any,
prop: m.toJSON() as any,
mprop: m,
name: key,
},
});
});
filtered = filtered.sort((a, b) => {
if (a.data?.name.startsWith("new_prop")) return 1;
if (b.data?.name.startsWith("new_prop")) return -1;
if (
a.data &&
b.data &&
typeof a.data.prop.idx !== "undefined" &&
typeof b.data.prop.idx !== "undefined"
) {
return a.data.prop.idx - b.data.prop.idx;
}
return 0;
});
}
const grouped: Record<string, NodeModel<PropItem>[]> = {};
for (const item of filtered) {
let [group, ...names] = item.text.split("__");
let name = names.join("_");
if (!name && !item.text.endsWith("__")) {
name = group;
group = "_";
}
if (!grouped[group]) {
grouped[group] = [];
}
grouped[group].push(item);
}
let final_tree: NodeModel<PropItem>[] = [];
let root_tree: NodeModel<PropItem>[] = [];
for (const items of Object.values(grouped)) {
const idx = items.findIndex((e) => e.text.endsWith("__"));
const plucked = items.splice(idx, 1);
items.unshift(plucked[0]);
for (const item of items) {
if (item.data?.name.includes("__")) {
final_tree.push(item);
} else {
root_tree.push(item);
}
}
}
root_tree = root_tree.sort((a, b) => {
return (a.data?.prop?.idx || 0) - (b.data?.prop?.idx || 0);
});
final_tree = [...root_tree, ...final_tree];
let hide = localStorage.getItem("prasi-prop-hide")?.split(",") || [];
return (
<div className="flex flex-col text-[12px] flex-1">
<div className="flex border-b p-1 h-[28px] items-center bg-slate-50 justify-between select-none">
<div
className="border flex items-center cursor-pointer bg-white hover:bg-blue-100 space-x-[1px] pr-[5px]"
onClick={() => {
p.ui.side.prop = false;
p.render();
}}
>
<div
className={css`
margin-bottom: 1px;
`}
dangerouslySetInnerHTML={{
__html: `
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9 4L9 11L4.5 7.5L9 4Z" fill="currentColor"></path></svg>`,
}}
></div>
<div>Style</div>
</div>
<div
className={cx(
"flex items-center space-x-1 cursor-pointer",
!!item.component?.useStyle &&
"border border-green-600 px-1 rounded-sm text-green-700"
)}
onClick={() => {
const useStyle = !!meta.mitem?.get("component")?.get("useStyle");
meta.mitem?.get("component")?.set("useStyle", !useStyle);
treeRebuild(p);
p.render();
}}
>
{!item.component?.useStyle ? <ToggleOff /> : <ToggleOn />}
<div className={cx("mt-[2px]")}>Inherit Style</div>
</div>
<div
className="border pl-1 cursor-pointer bg-white hover:bg-blue-100 flex items-center"
onClick={() => {
if (active.comp_id) {
active.comp_id = active.instance.comp_id || "";
active.item_id = active.instance.item_id || "";
active.instance.comp_id = "";
active.instance.item_id = "";
p.render();
}
}}
>
<div>Instance</div>
<div
className={css`
margin-bottom: 1px;
`}
dangerouslySetInnerHTML={{
__html: `
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 11L6 4L10.5 7.5L6 11Z" fill="currentColor"></path></svg>`,
}}
></div>
</div>
</div>
<div className="flex flex-1 relative overflow-y-auto overflow-x-hidden">
<div
className={cx("absolute inset-0")}
ref={(ref) => (propRef.el = ref)}
>
<DndProvider
backend={HTML5Backend}
options={getBackendOptions({
html5: {
rootElement: propRef.el,
},
})}
>
<TypedTree
tree={final_tree}
sort={false}
onDrop={(tree, { dragSourceId, relativeIndex }) => {
const doc = meta.mitem?.doc;
doc?.transact(() => {
mprops?.forEach((p, key) => {
const idx = tree.findIndex(
(e) => `${item.id}-${key}` === e.id
);
if (idx >= 0) {
p.set("idx", idx);
}
});
});
}}
render={(node, params) => (
<EdPropCompTreeItem
node={node}
render={render}
params={params}
hidden={
node.text.includes("__") &&
hide.includes(node.text.split("__")[0])
}
toggleExpand={(node) => {
const group = node.text.split("__")[0];
if (hide.includes(group)) {
hide = hide.filter((e) => e !== group);
} else {
hide.push(group);
}
localStorage.setItem("prasi-prop-hide", hide?.join(","));
local.render();
}}
/>
)}
rootId={"root"}
classes={treeClasses}
dropTargetOffset={10}
initialOpen={true}
placeholderRender={(node, params) => (
<Placeholder node={node} params={params} />
)}
dragPreviewRender={() => <></>}
/>
</DndProvider>
<div className="flex">
<div
className="m-1 border border-blue-200 px-2 self-start text-[13px] hover:bg-blue-100 cursor-pointer select-none flex-1 h-[22px]"
onClick={() => {
if (mprops) {
const indexes: (number | undefined)[] = [];
mprops.forEach((e) => indexes.push(e.get("idx")));
let idx: any = (indexes.sort().pop() || 0) + 1;
if (indexes.length === 0) {
idx = 1;
} else {
idx = parseInt(idx) + 1;
}
let name = `new_prop_${idx}`;
while (mprops.get(name)) {
idx = parseInt(idx) + 1;
name = `new_prop_${idx}`;
}
const map = new Y.Map() as FMCompDef;
syncronize(map, {
idx,
name,
type: "string",
value: '"hello"',
valueBuilt: '"hello"',
meta: {
type: "text",
},
});
mprops.set(name, map);
propPopover.name = name;
p.render();
}
}}
>
+ New Prop
</div>{" "}
<Popover
content={
<div
className={cx(css`
width: 700px;
height: 500px;
margin: 5px 0px;
`)}
>
<SimpleMonaco
onChange={(value) => {
local.typings = value;
local.render();
}}
value={local.typings}
lang="typescript"
/>
</div>
}
open={local.openTypings}
onOpenChange={(open) => {
try {
if (mcomp) {
if (!open) {
mcomp.set("typings", local.typings);
treeRebuild(p);
p.render();
} else {
local.typings =
mcomp.get("typings") ||
`\
const typings = {
_raw: {
}
}`;
}
}
} catch (e) {
console.log(e);
}
local.openTypings = open;
local.render();
}}
>
<div className="m-1 ml-0 border border-blue-200 px-2 self-start text-[13px] hover:bg-blue-100 cursor-pointer select-none flex items-center space-x-1 text-xs h-[22px]">
<span>Types</span>
</div>
</Popover>
<Popover
content={
<div
className={cx(css`
width: 700px;
height: 500px;
margin: 5px 0px;
`)}
>
<SimpleMonaco
onChange={(value) => {
local.json = value;
local.render();
}}
value={local.json}
lang="json"
/>
</div>
}
open={local.openJSON}
onOpenChange={(open) => {
try {
if (!open) {
syncronize(mprops as any, JSON.parse(local.json));
treeRebuild(p);
p.render();
} else {
local.json = JSON.stringify(mprops?.toJSON(), null, 2);
}
} catch (e) {
console.log(e);
}
local.openJSON = open;
local.render();
}}
>
<div className="m-1 ml-0 border border-blue-200 px-2 self-start text-[13px] hover:bg-blue-100 cursor-pointer select-none flex items-center space-x-1 text-xs h-[22px]">
<span>JSON</span>
</div>
</Popover>
</div>
</div>
</div>
</div>
);
};
const treeClasses = {
container: "flex flex-col flex-1",
dropTarget: "drop-target",
placeholder: "placeholder",
draggingSource: css`
background: #e4f0ff;
cursor: not-allowed;
`,
};
export const Placeholder: FC<{
node: Parameters<PlaceholderRender<PropItem>>[0];
params: Parameters<PlaceholderRender<PropItem>>[1];
}> = ({ params }) => {
return (
<div
className={cx(
"flex items-center bg-blue-50",
css`
height: 10px;
z-index: 99;
position: absolute;
left: 0px;
transform: translateY(-50%);
right: 0px;
`
)}
>
<div
className={cx(
"flex-1",
css`
background-color: #1b73e8;
height: 2px;
`
)}
></div>
</div>
);
};
const ToggleOn = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
version="1.1"
viewBox="0 0 122.878 73.391"
xmlSpace="preserve"
>
<path
fillRule="evenodd"
fill="currentColor"
d="M35.691 0h51.495c19.631 0 35.691 16.513 35.691 36.695S106.815 73.39 87.186 73.39H35.691C16.062 73.391 0 56.877 0 36.695S16.061 0 35.691 0zm50.492 7.863c16.184 0 29.309 13.125 29.309 29.309S102.366 66.48 86.183 66.48 56.874 53.355 56.874 37.172 69.999 7.863 86.183 7.863z"
clipRule="evenodd"
></path>
</svg>
);
const ToggleOff = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
x="0"
y="0"
version="1.1"
viewBox="0 0 122.882 73.393"
xmlSpace="preserve"
>
<path
fillRule="evenodd"
d="M87.189 0H35.694C16.063 0 0 16.513 0 36.697c0 20.182 16.063 36.695 35.694 36.695H87.19c19.632 0 35.692-16.514 35.692-36.695C122.882 16.513 106.821 0 87.189 0zM59.367 6.057a37.56 37.56 0 014.846 4.098l.022.022.073.07a37.324 37.324 0 012.621 2.939 37.495 37.495 0 018.288 23.349c.012.068.014.141.014.213V36.834l-.002.039v.021l-.012.061a37.491 37.491 0 01-3.959 16.615 37.634 37.634 0 01-7.046 9.777l-.021.02c-.311.311-.671.652-1.05 1.008a37.606 37.606 0 01-3.637 2.957h25.974l.18-.008h.1l.032.002h.029l.029.006a28.57 28.57 0 0013.018-3.221 30.04 30.04 0 007.72-5.742l.012-.012-.003-.004.023-.023.052-.049a29.115 29.115 0 002.08-2.389l.002-.002-.002-.002a30.487 30.487 0 001.882-2.678 31.075 31.075 0 004.722-16.381l-.01-.129v-.012l.005-.033v-.021l.002-.045v-.016l.007-.045a31.218 31.218 0 00-2.343-11.718 30.9 30.9 0 00-6.427-9.785L106.57 15l-.003.002-.011-.012h-.003l-.02-.021-.01-.016a29.961 29.961 0 00-2.338-2.146 29.773 29.773 0 00-2.594-1.927c-4.57-3.02-9.999-4.791-15.791-4.818l-.139.007h-.002l-.027-.004h-.09l-.027-.007H59.367v-.001zM36.696 7.864c-16.183 0-29.308 13.124-29.308 29.308 0 16.186 13.125 29.311 29.308 29.311 16.185 0 29.311-13.125 29.311-29.311 0-16.184-13.126-29.308-29.311-29.308z"
clipRule="evenodd"
></path>
</svg>
);