@ovine/craft
Version:
Ovine json editor.
158 lines (157 loc) • 7.21 kB
JavaScript
/**
* 组件选择器
* TODO:
* 1. 修复 当无滚动区域时,每次都nav自动跳到最后一个问题。(这是bootstrap问题,完全理解不了为什么,要选到最后一项)
* 正确逻辑是:默认不做任何操作
* https://github.com/twbs/bootstrap/blob/main/js/src/scrollspy.js#L209
* 2. 完成 选择后 将 组件添加到对应位置
*/
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { Drawer } from 'amis';
import { flattenTree, filterTree } from 'amis/lib/utils/helper';
import _ from 'lodash';
import React, { useEffect, useMemo, useRef } from 'react';
import { app } from '@core/app';
import { useImmer, useSubscriber } from '@core/utils/hooks';
import { publish } from '@core/utils/message';
import { onAddNode } from "../preview/actions";
import { message, domId } from "../../constants";
import nodeConfig from "./nodes";
import { StyledSelector } from "./styled";
const Selector = (props) => {
const { label: headerLabel = '添加组件' } = props.info;
const [state, setState] = useImmer({
searchText: '',
});
const storeRef = useRef({});
const { searchText } = state;
const items = useMemo(() => {
// 没有查询时 返回所有数据
if (!searchText) {
return flattenTree(nodeConfig);
}
const filteredItems = filterTree(nodeConfig, (item) => {
const { type = '', label = '', parent } = item;
// 父级 默认全部通过匹配,直接判断 子级 的数据
if (!parent) {
return true;
}
// 只判断 type 与 label 两个数据
return type.indexOf(searchText) > -1 || label.indexOf(searchText) > -1;
});
return flattenTree(
// 过滤掉 没有 子级 的数据
filterTree(filteredItems, (item) => (!item.parent && !!item.children.length) || !!item.parent));
}, [searchText]);
const onSearchChange = _.throttle((e) => {
const { value } = e.currentTarget;
setState((d) => {
d.searchText = value;
});
}, 100);
const onItemClick = (item, e) => {
const $item = $(e.currentTarget);
$item
.addClass('active')
.siblings()
.removeClass('active');
onAddNode({
container: props.info,
node: item,
position: {
x: e.pageX,
y: e.pageY,
},
});
};
useEffect(() => {
// 使用了 bootstrap scrollspy 组件 https://getbootstrap.com/docs/4.5/components/scrollspy
const $scrollSpy = $(`#${domId.editorSelector} [data-spy="scroll"]`);
const $scrollNav = $(`#${domId.editorSelectorNav}`);
storeRef.current.$scrollSpy = $scrollSpy;
$scrollSpy.scrollspy();
$scrollNav.on('click', '.nav-link', (e) => {
const $item = $(e.target);
if ($item.hasClass('active')) {
return;
}
$scrollSpy.scrollspy('dispose');
$item
.addClass('active')
.siblings()
.removeClass('active');
const $targetItem = $($item.attr('href'));
$scrollSpy.animate({ scrollTop: $scrollSpy.scrollTop() + $targetItem.offset().top - $scrollSpy.offset().top }, 200, () => {
$scrollSpy.scrollspy('refresh');
});
});
return () => {
$scrollSpy.scrollspy('dispose');
};
}, []);
useEffect(() => {
const { $scrollSpy } = storeRef.current;
if (!items.length) {
$scrollSpy.scrollspy('dispose');
}
else {
$scrollSpy.scrollspy('refresh');
}
}, [items.length]);
return (React.createElement(StyledSelector, { id: domId.editorSelector },
React.createElement("div", { className: "selector-title" },
React.createElement("p", null, headerLabel)),
React.createElement("div", { className: "selector-input" },
React.createElement("div", { className: "input" },
React.createElement("input", { placeholder: "\u641C\u7D22\u53EF\u7528\u7EC4\u4EF6", value: searchText, onChange: onSearchChange }),
React.createElement("i", { className: "fa fa-search" }))),
items.length === 0 && (React.createElement("div", { className: "selector-empty" },
React.createElement("i", { className: "fa fa-chain-broken" }),
React.createElement("span", null, "\u6682\u672A\u627E\u5230\u5BF9\u5E94\u7EC4\u4EF6"))),
React.createElement("div", { className: "selector-list" },
React.createElement("div", { id: domId.editorSelectorNav, className: "selector-nav" }, items.map((item, index) => {
if (!item.parent) {
return (React.createElement("div", { key: index, className: "title nav-link", href: `#spy-${item.type}` },
React.createElement("i", { className: `fa fa-${item.icon} p-r-xs` }),
React.createElement("span", null, item.label)));
}
return (React.createElement("div", { key: index, className: "item nav-link", href: `#spy-${item.parent}-${item.type}` }, item.label));
})),
React.createElement("div", { className: "selector-content", "data-target": `#${domId.editorSelectorNav}`, "data-spy": "scroll" }, items.map((item, index) => {
if (!item.parent) {
return (React.createElement("div", { key: index, id: `spy-${item.type}`, className: "title" }, item.label));
}
return (React.createElement("div", { key: index, id: `spy-${item.parent}-${item.type}`, className: "item", onClick: (e) => onItemClick(item, e) },
React.createElement("img", { src: item.img, alt: item.label }),
React.createElement("div", null,
React.createElement("h6", null, item.label),
React.createElement("p", null, item.desc))));
})))));
};
export const toggleSelector = (info = {}) => {
publish(message.toggleSelector, info);
};
export default () => {
const [info, setInfo] = useImmer({});
const { isShow = false } = info, resetInfo = __rest(info, ["isShow"]);
const onHide = () => {
setInfo((d) => {
d.isShow = false;
});
};
useSubscriber(message.toggleSelector, (data = {}) => {
setInfo(() => data);
});
return (React.createElement(Drawer, { theme: app.theme.getName(), size: "md", onHide: onHide, show: isShow, position: "right" },
React.createElement(Selector, { info: resetInfo })));
};