import { FC, Fragment, useCallback, useEffect } from "react"; import { useGlobal, useLocal } from "web-utils"; import { apiProxy } from "../../../../../base/load/api/api-proxy"; import { dbProxy } from "../../../../../base/load/db/db-proxy"; import { FMCompDef, FNCompDef } from "../../../../../utils/types/meta-fn"; import { EDGlobal, IMeta, active } from "../../../logic/ed-global"; import { treeRebuild } from "../../../logic/tree/build"; import { EdPropLabel } from "./prop-label"; import { ChevronDown } from "../../tree/node/item/indent"; import { Popover } from "../../../../../utils/ui/popover"; import { propInstanceOnChange } from "./on-change"; import { Tooltip } from "../../../../../utils/ui/tooltip"; type MetaOption = { label: string; alt?: string; value: any; checked?: boolean; options?: MetaOption[]; reload?: string[]; }; const config = { opt: {} as Record void>, }; export const EdPropInstanceOptions: FC<{ meta: IMeta; name: string; mprop: FMCompDef; cprop: FNCompDef; label: string; labelClick?: React.MouseEventHandler | undefined; }> = ({ name, mprop, cprop, label, labelClick, meta }) => { const prop = mprop.toJSON() as FNCompDef; const local = useLocal( { codeEditing: false, loading: true, isOpen: false, val: "", metaFnInit: false, metaFn: null as null | (() => Promise), checkbox: { width: 0, }, options: [] as MetaOption[], optDeps: [] as any[], resetOnDeps: false as boolean | (() => any[]), open: false, pendingVal: null as any, changedTimeout: null as any, }, ({ setDelayedRender }) => { setDelayedRender(true); } ); const p = useGlobal(EDGlobal, "EDITOR"); config.opt[name] = () => { local.metaFn = null; local.loading = false; local.render(); }; useEffect(() => { local.metaFnInit = false; local.render(); }, [name, active.item_id]); if (cprop.meta?.options || cprop.meta?.optionsBuilt) { if (!local.metaFn || local.optDeps.length > 0) { let fn = "" as any; let arg = {}; try { if (p.site.config.api_url) { if (!p.script.db) p.script.db = dbProxy(p.site.config.api_url); if (!p.script.api) p.script.api = apiProxy(p.site.config.api_url); } arg = { _meta: meta, _metas: p.page.meta, ...window.exports, db: p.script.db, api: p.script.api, ...active.scope, }; if (meta.item.script?.props) { for (const [k, v] of Object.entries(meta.item.script?.props)) { if (v.value && v.value.length > 3) { try { const evn = new Function("arg", `arg["${k}"] = ${v.value}`); evn(arg); } catch (e) {} } } } if (meta.item.component) { for (const [k, v] of Object.entries(meta.item.component.props)) { if (v.valueBuilt && v.valueBuilt.length > 3) { if (v.valueBuilt.startsWith(`const _jsxFileName = "";`)) { v.valueBuilt = `(() => { ${v.valueBuilt.replace( `const _jsxFileName = "";`, `const _jsxFileName = ""; return ` )} })()`; } try { const evn = new Function( "arg", `arg["${k}"] = ${v.valueBuilt}` ); evn(arg); } catch (e) { console.error(e); console.error(k, v.valueBuilt); } } if (v.content) { eval( `try { arg.__${k} = ${JSON.stringify( v.content )} } catch(e) { console.error("arg", e); }` ); } } } const src = ( cprop.meta.optionsBuilt || cprop.meta.options || "" ).trim(); const final_src = ` try { const resOpt = ${src.endsWith(";") ? src : `${src};`} if (typeof resOpt === 'function') local.metaFn = resOpt; else { if (typeof resOpt === 'object' && Array.isArray(resOpt.deps) && typeof resOpt.fn === 'function') { local.metaFn = resOpt.fn; local.optDeps = resOpt.deps; local.resetOnDeps = resOpt.reset; } else { local.options = resOpt; } } } catch(e) { console.error(e); }`; fn = new Function(...Object.keys(arg), "local", final_src); fn(...Object.values(arg), local); } catch (e) { console.error(e); console.error(fn.toString(), arg); } } } const metaFnCallback = useCallback( async (e: any) => { local.loading = false; local.options = e; if (local.resetOnDeps) { if (!local.metaFnInit) { local.metaFnInit = true; } else { let reset = "[]"; if (typeof local.resetOnDeps === "function") { reset = JSON.stringify(local.resetOnDeps()); } await mprop.doc?.transact(() => { mprop.set("value", reset); mprop.set("valueBuilt", reset); }); await treeRebuild(p); p.render(); } } local.render(); }, [local.metaFnInit, local.resetOnDeps, mprop] ); useEffect(() => { if (local.metaFn) { local.loading = true; try { const res = local.metaFn(); if (res instanceof Promise) { res.then(metaFnCallback).catch((e) => { console.error( `ERROR in component ${meta.item.name}, prop ${name}:` ); console.error(local.metaFn?.toString()); console.error(e); }); } else metaFnCallback(res); } catch (e) { console.error(e); } } else { local.loading = false; local.render(); } }, [...local.optDeps]); let evalue: any = null; try { eval(`evalue = ${prop.value}`); } catch (e) {} if (local.open) { evalue = local.pendingVal; } else { local.pendingVal = evalue; } useEffect(() => { if (Array.isArray(local.options) && !Array.isArray(evalue)) { if (mode !== "checkbox") { local.val = evalue; local.render(); } } }, [evalue]); const onChange = useCallback( (val: string, item: MetaOption | undefined) => { if (local.open) { eval(`local.pendingVal = ${val}`); local.render(); return; } mprop.doc?.transact(() => { mprop.set("value", val); mprop.set("valueBuilt", val); }); treeRebuild(p); p.render(); clearTimeout(local.changedTimeout); local.changedTimeout = setTimeout(() => { propInstanceOnChange(p, name, val); }, 1000); setTimeout(() => { if (item?.reload) { for (const name of item.reload) { if (config.opt[name]) { config.opt[name](); } } } }); }, [local.open, mprop, config?.opt] ); let mode = cprop.meta?.option_mode; if (!mode) mode = "button"; if (local.options && local.options.length > 0) { for (const [k, v] of Object.entries(local.options)) { if (typeof v === "string") { local.options[k as any] = { label: v, value: v }; } } } return (
{local.loading ? (
Loading...
) : ( <> {mode === "dropdown" && ( )} {mode === "button" && (
{Array.isArray(local.options) && local.options.map((item, idx) => { return (
{ onChange(`"${item.value}"`, item); }} > {item.label}
); })}
)} {mode === "checkbox" && ( { local.open = open; local.render(); if (!open) { onChange(JSON.stringify(local.pendingVal), null as any); } else { local.pendingVal = null; local.render(); } }} open={local.open} content={
{Array.isArray(local.options) && local.options.map((item, idx) => { const val: any[] = Array.isArray(evalue) ? evalue : []; const found = val.find((e) => { if (!item.options) { return e === item.value; } else { if ( typeof e === "object" && e.value === item.value ) { return true; } return false; } }); return ( { onChange(JSON.stringify(val), item); local.render(); }} found={found} render={local.render} /> ); })}
} asChild >
{ local.open = true; local.render(); }} ref={(el) => { if (!local.checkbox.width && el) { const bound = el.getBoundingClientRect(); local.checkbox.width = bound.width + 100; setTimeout(local.render, 500); } }} >
{Array.isArray(evalue) ? evalue.length === 0 ? "Select Item" : `${evalue.length} selected` : `Select Item`}
)} )}
); }; const SingleCheckbox = ({ val, item, idx, onChange, depth, found, render, }: { item: MetaOption; idx: number; depth: number; val: any[]; found: any; onChange: (val: MetaOption[], item: MetaOption) => void; render: () => void; }) => { const is_check = !!val.find((e) => { if (!item.options) { return e === item.value; } else { if (typeof e === "object" && e.value === item.value) { return true; } return false; } }); const toggleCheck = () => { if (item.options) { let idx = val.findIndex((e) => { if (typeof e === "object" && e.value === item.value) { return true; } return false; }); if (idx >= 0) { val.splice(idx, 1); } else { val.push({ value: item.value, checked: [] }); } } else { if (item.value) { let idx = val.findIndex((e) => e === item.value); if (idx >= 0) { val.splice(idx, 1); } else { val.push(item.value); } } } onChange(val, item); }; useEffect(() => { if (item.checked && !is_check) { toggleCheck(); } }, []); return ( <>
{ toggleCheck(); }} > {!is_check ? unchecked : checked}
{item.label.length > 15 ? ( {item.label.substring(0, 15) + "..."} ) : ( item.label )}
{item.alt}
{item.options && found && item.options.map((child, idx) => { const sub_found = found.checked.find((e: any) => { if (!item.options) { return e === child.value; } else { if (typeof e === "object" && e.value === child.value) { return true; } return false; } }); return ( { onChange(val, child); render(); }} render={render} /> ); })} ); }; const checked = ( ); const unchecked = ( );