fix master detail
This commit is contained in:
parent
12f6164682
commit
e096b9dd34
|
|
@ -1,5 +1,5 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { FC, ReactNode, useEffect } from "react";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
|
||||
export type BreadItem = {
|
||||
|
|
@ -8,85 +8,55 @@ export type BreadItem = {
|
|||
onClick?: (ev: any) => void;
|
||||
};
|
||||
|
||||
const breadcrumbData = {} as Record<string, any>;
|
||||
|
||||
type BreadcrumbProps = {
|
||||
on_load?: () => Promise<BreadItem[]>;
|
||||
className?: string;
|
||||
props?: any;
|
||||
value?: BreadItem[];
|
||||
item?: any
|
||||
item?: PrasiItem;
|
||||
};
|
||||
|
||||
export const Breadcrumb: FC<BreadcrumbProps> = (_arg) => {
|
||||
const { on_load, item } = _arg;
|
||||
export const Breadcrumb: FC<BreadcrumbProps> = ({
|
||||
value,
|
||||
className,
|
||||
on_load,
|
||||
item,
|
||||
}) => {
|
||||
const local = useLocal({
|
||||
list: _arg.value || ([] as BreadItem[]),
|
||||
list: [] as BreadItem[],
|
||||
status: "init" as "init" | "loading" | "ready",
|
||||
params: {},
|
||||
});
|
||||
// code review: md.breadcrumb yang di set di props value dipindahkan di on_load
|
||||
// dan ketika panjang _arg.value bernilai null maka status berubah menjadi init
|
||||
// untuk menjalankan on_load
|
||||
// case: ketika refreshBread dijalankan pada MasterDetail
|
||||
// md.breadcrum yang awalnya array kosong akan berisi satu array namun
|
||||
// md.breadcrumb yang diterima di komponen ini tidak berubah tetap seperti
|
||||
// value default yakni array kosong
|
||||
if (_arg.value) {
|
||||
local.list = _arg.value;
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
local.list = value;
|
||||
local.status = "ready";
|
||||
if(!_arg.value.length) local.status = "init"
|
||||
}
|
||||
if (local.status === "init") {
|
||||
let should_load = true;
|
||||
local.status = "loading";
|
||||
if (isEditor && item && breadcrumbData[item.id]) {
|
||||
local.list = breadcrumbData[item.id];
|
||||
local.status = "ready";
|
||||
should_load = false;
|
||||
if(!local.list.length) should_load = true;
|
||||
}
|
||||
if (should_load && typeof on_load === "function") {
|
||||
const callback = (res: any) => {
|
||||
local.list = res;
|
||||
if (item) breadcrumbData[item.id] = res;
|
||||
local.status = "ready";
|
||||
};
|
||||
const res = on_load();
|
||||
if (res instanceof Promise) {
|
||||
res.then((res) => {
|
||||
callback(res);
|
||||
local.render();
|
||||
});
|
||||
} else callback(res);
|
||||
}
|
||||
}
|
||||
|
||||
if (isEditor && local.status !== "ready") {
|
||||
if (item && breadcrumbData[item.id]) {
|
||||
local.list = breadcrumbData[_arg.item.id];
|
||||
}
|
||||
if (typeof on_load === "function") {
|
||||
local.status = "loading";
|
||||
on_load().then((list) => {
|
||||
local.list = list;
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
});
|
||||
}
|
||||
|
||||
local.render();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"breadcrumb c-w-full c-flex c-items-center c-flex-wrap",
|
||||
_arg.className
|
||||
className
|
||||
)}
|
||||
>
|
||||
{local.status !== "ready" ? (
|
||||
<Skeleton className="c-h-4 c-w-[80%]" />
|
||||
) : (
|
||||
<>
|
||||
{local.list === null ? (
|
||||
<>
|
||||
<h1 className="c-font-semibold c-text-xs md:c-text-base">
|
||||
Null Breadcrumb
|
||||
</h1>
|
||||
</>
|
||||
) : (
|
||||
{local.list &&
|
||||
(local.list || []).map((item, index): ReactNode => {
|
||||
const lastIndex = local.list.length - 1;
|
||||
|
||||
|
|
@ -128,8 +98,7 @@ export const Breadcrumb: FC<BreadcrumbProps> = (_arg) => {
|
|||
)}
|
||||
</>
|
||||
);
|
||||
})
|
||||
)}
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
import { FMLocal } from "../typings";
|
||||
import { BaseLabel } from "./BaseLabel";
|
||||
import { BaseFieldProps } from "./utils/type/field";
|
||||
import { useField } from "./utils/use-field";
|
||||
|
||||
export const BaseField = <T extends Record<string, any>>(
|
||||
arg: BaseFieldProps<T> & { fm: FMLocal }
|
||||
) => {
|
||||
const field = useField<T>(arg);
|
||||
const fm = arg.fm;
|
||||
const mode = fm.props.label_mode || "vertical";
|
||||
const props = arg.props;
|
||||
const w = field.width;
|
||||
const errors = fm.error.get(field.name);
|
||||
|
||||
return (
|
||||
<label
|
||||
className={cx(
|
||||
"field",
|
||||
"c-flex",
|
||||
css`
|
||||
padding: 5px 0px 0px 10px;
|
||||
`,
|
||||
w === "auto" && fm.size.field === "full" && "c-w-full",
|
||||
w === "auto" && fm.size.field === "half" && "c-w-1/2",
|
||||
w === "full" && "c-w-full",
|
||||
w === "¾" && "c-w-3/4",
|
||||
w === "½" && "c-w-1/2",
|
||||
w === "⅓" && "c-w-1/3",
|
||||
w === "¼" && "c-w-1/4",
|
||||
mode === "horizontal" && "c-flex-row c-items-center",
|
||||
mode === "vertical" && "c-flex-col c-space-y-1"
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{mode !== "hidden" && <BaseLabel field={field} fm={fm} />}
|
||||
<div className="field-inner c-flex c-flex-1 c-flex-col">
|
||||
{field.desc && (
|
||||
<div className={cx("c-p-2 c-text-xs", errors.length > 0 && "c-pb-1")}>
|
||||
{field.desc}
|
||||
</div>
|
||||
)}
|
||||
{errors.length > 0 && (
|
||||
<div
|
||||
className={cx(
|
||||
"c-p-2 c-text-xs c-text-red-600",
|
||||
field.desc && "c-pt-0"
|
||||
)}
|
||||
>
|
||||
{errors.map((err) => {
|
||||
return <div>{err}</div>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC } from "react";
|
||||
import { BaseField } from "./BaseField";
|
||||
import { initSimpleForm as initBaseForm } from "./utils/init";
|
||||
import { BaseFieldProps, BaseFormProps } from "./utils/type/field";
|
||||
|
||||
type Children<T extends Record<string, any>> = Exclude<
|
||||
BaseFormProps<T>["children"],
|
||||
undefined
|
||||
>;
|
||||
|
||||
export const BaseForm = <T extends Record<string, any>>(
|
||||
arg: BaseFormProps<T>
|
||||
) => {
|
||||
const fm = useLocal<FMInternal>({ status: "init" } as any);
|
||||
const local = useLocal({
|
||||
Field: null as null | FC<BaseFieldProps<T>>,
|
||||
children: null as unknown as Children<T>,
|
||||
});
|
||||
|
||||
if (fm.status === "init") {
|
||||
local.Field = (props) => {
|
||||
return <BaseField fm={fm} {...props} />;
|
||||
};
|
||||
if (arg.children) local.children = arg.children;
|
||||
else {
|
||||
local.children = ({ Field }) => {
|
||||
const data = Object.entries(fm.data);
|
||||
return (
|
||||
<>
|
||||
{data.map(([key, value]) => {
|
||||
return <Field name={key} />;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
initBaseForm(fm, arg);
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
fm.submit();
|
||||
}}
|
||||
>
|
||||
{local.Field && local.children({ Field: local.Field })}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { BaseFieldLocal } from "./utils/type/field";
|
||||
import { FMLocal } from "../typings";
|
||||
|
||||
export const BaseLabel = <T extends Record<string, any>>({
|
||||
field,
|
||||
fm,
|
||||
}: {
|
||||
field: BaseFieldLocal<T>;
|
||||
fm: FMLocal;
|
||||
}) => {
|
||||
const errors = fm.error.get(field.name);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"label c-text-sm c-flex c-items-center",
|
||||
fm.props.label_mode === "horizontal" &&
|
||||
css`
|
||||
width: ${fm.props.label_width}px;
|
||||
`,
|
||||
fm.props.label_mode === "vertical" && "c-mt-3"
|
||||
)}
|
||||
>
|
||||
<span className={cx(errors.length > 0 && `c-text-red-600`)}>
|
||||
{field.label}
|
||||
</span>
|
||||
{field.required && (
|
||||
<span className="c-text-red-600 c-mb-2 c-ml-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M12 6v12" />
|
||||
<path d="M17.196 9 6.804 15" />
|
||||
<path d="m6.804 9 10.392 6" />
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { FMLocal } from "../../typings";
|
||||
import { formError } from "../../utils/error";
|
||||
import { reloadBaseForm } from "./reload";
|
||||
import { submitBaseForm } from "./submit";
|
||||
import { BaseFormProps } from "./type/field";
|
||||
|
||||
export const initSimpleForm = <T extends Record<string, any>>(
|
||||
fm: FMLocal,
|
||||
arg: BaseFormProps<T>
|
||||
) => {
|
||||
fm.data = {};
|
||||
fm.error = formError(fm);
|
||||
fm.events = {
|
||||
on_change(name, new_value) {},
|
||||
};
|
||||
fm.field_def = {};
|
||||
fm.fields = {};
|
||||
fm.internal = {
|
||||
submit: { done: [], promises: [], timeout: null as any },
|
||||
reload: { done: [], promises: [], timeout: null as any },
|
||||
};
|
||||
fm.props = {} as any;
|
||||
fm.reload = async () => {
|
||||
await reloadBaseForm(fm, arg);
|
||||
};
|
||||
fm.size = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
field: "full",
|
||||
};
|
||||
fm.submit = () => {
|
||||
return submitBaseForm(fm, arg);
|
||||
};
|
||||
fm.status = "ready";
|
||||
};
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { FMLocal } from "../../typings";
|
||||
import { BaseFormProps } from "./type/field";
|
||||
|
||||
export const reloadBaseForm = async <T extends Record<string, any>>(
|
||||
fm: FMLocal,
|
||||
arg: BaseFormProps<T>
|
||||
) => {};
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { FMLocal } from "../../typings";
|
||||
import { BaseFormProps } from "./type/field";
|
||||
|
||||
export const submitBaseForm = async <T extends Record<string, any>>(
|
||||
fm: FMLocal,
|
||||
arg: BaseFormProps<T>
|
||||
) => {
|
||||
return true;
|
||||
};
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import { FC, ReactNode } from "react";
|
||||
|
||||
|
||||
export type BaseFieldWidth =
|
||||
| "auto"
|
||||
| "full"
|
||||
| "¾"
|
||||
| "½"
|
||||
| "⅓"
|
||||
| "¼"
|
||||
| "1/2"
|
||||
| "1/3"
|
||||
| "1/4"
|
||||
| "3/4";
|
||||
|
||||
export type BaseFieldType = "text" | "relation";
|
||||
export type BaseFieldProps<T extends Record<string, any>> = {
|
||||
name: keyof T;
|
||||
label?: string;
|
||||
type?: BaseFieldType;
|
||||
props?: any;
|
||||
desc?: string;
|
||||
on_change?: (arg: { value: any }) => void | Promise<void>;
|
||||
|
||||
prefix?: any;
|
||||
suffix?: any;
|
||||
|
||||
required?: boolean;
|
||||
required_msg?: (name: string) => string;
|
||||
disabled?: boolean;
|
||||
|
||||
width?: BaseFieldWidth;
|
||||
};
|
||||
|
||||
export type BaseFieldInternal<T extends Record<string, any>> = {
|
||||
name: keyof T;
|
||||
label: ReactNode;
|
||||
type: BaseFieldType;
|
||||
desc: string;
|
||||
on_change: (arg: { value: any }) => void | Promise<void>;
|
||||
|
||||
prefix: any;
|
||||
suffix: any;
|
||||
|
||||
required: boolean;
|
||||
required_msg: (name: string) => string;
|
||||
disabled: boolean;
|
||||
|
||||
width: BaseFieldWidth;
|
||||
status: "init" | "loading" | "ready";
|
||||
|
||||
PassProp: any;
|
||||
child: any;
|
||||
};
|
||||
|
||||
export type BaseFieldLocal<T extends Record<string, any>> =
|
||||
BaseFieldInternal<T>;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { FC, ReactNode } from "react";
|
||||
import { BaseFieldProps } from "./field";
|
||||
|
||||
export type BaseFormProps<T extends Record<string, any>> = {
|
||||
onLoad: () => Promise<T | null>;
|
||||
onSubmit: (arg: { data: T | null }) => Promise<boolean>;
|
||||
children?: (arg: { Field: FC<BaseFieldProps<T>> }) => ReactNode;
|
||||
};
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FMLocal } from "../../typings";
|
||||
import {
|
||||
BaseFieldInternal,
|
||||
BaseFieldLocal,
|
||||
BaseFieldProps,
|
||||
} from "./type/field";
|
||||
import { formatName } from "@/gen/utils";
|
||||
|
||||
export const useField = <T extends Record<string, any>>(
|
||||
arg: BaseFieldProps<T> & { fm: FMLocal }
|
||||
) => {
|
||||
const fm = arg.fm;
|
||||
const local = useLocal<BaseFieldInternal<T>>({
|
||||
status: "init",
|
||||
} as any) as BaseFieldLocal<T>;
|
||||
|
||||
if (local.status === "init") {
|
||||
local.name = arg.name;
|
||||
local.label = arg.label || formatName(arg.name as string);
|
||||
local.type = arg.type || "text";
|
||||
local.desc = arg.desc || "";
|
||||
local.status = "ready";
|
||||
local.width = arg.width || fm.size.field === "full" ? "full" : "1/2";
|
||||
}
|
||||
|
||||
return local as BaseFieldLocal<T>;
|
||||
};
|
||||
|
|
@ -7,10 +7,9 @@ import { Label } from "./Label";
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
|
||||
export const Field: FC<FieldProp> = (arg) => {
|
||||
console.log({arg})
|
||||
const { fm } = arg;
|
||||
const field = useField(arg);
|
||||
const name = typeof field.name === 'function' ? field.name() : field.name;
|
||||
const name = field.name;
|
||||
const local = useLocal({ prev_val: fm.data[name] });
|
||||
|
||||
const mode = fm.props.label_mode;
|
||||
|
|
@ -56,9 +55,7 @@ export const Field: FC<FieldProp> = (arg) => {
|
|||
fm={fm}
|
||||
PassProp={arg.PassProp}
|
||||
child={arg.child}
|
||||
_meta={arg._meta}
|
||||
_item={arg._item}
|
||||
_sync={arg._sync}
|
||||
arg={arg}
|
||||
/>
|
||||
{field.desc && (
|
||||
|
|
|
|||
|
|
@ -1,16 +1,9 @@
|
|||
import { createItem } from "@/gen/utils";
|
||||
import get from "lodash.get";
|
||||
import { FC, isValidElement, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp, FieldTypeCustom } from "../typings";
|
||||
import { genFieldMitem, updateFieldMItem } from "../utils/gen-mitem";
|
||||
import { fieldMapping } from "./mapping";
|
||||
import { FC, isValidElement } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../typings";
|
||||
import { FieldLoading } from "./raw/FieldLoading";
|
||||
import { TypeCustom } from "./type/TypeCustom";
|
||||
import { FieldTypeText, PropTypeText } from "./type/TypeText";
|
||||
import { TypeDropdown } from "./type/TypeDropdown";
|
||||
import { FieldToggle } from "./type/TypeToggle";
|
||||
import { SingleOption } from "./type/TypeSingleOption";
|
||||
import { MultiOption } from "./type/TypeMultiOption";
|
||||
import { SingleOption } from "./type/TypeSingleOption";
|
||||
import { FieldTypeText, PropTypeText } from "./type/TypeText";
|
||||
|
||||
const modify = {
|
||||
timeout: null as any,
|
||||
|
|
@ -21,15 +14,13 @@ export const FieldInput: FC<{
|
|||
fm: FMLocal;
|
||||
PassProp: any;
|
||||
child: any;
|
||||
_item: any;
|
||||
_meta: any;
|
||||
_sync: (mitem: any, item: any) => void;
|
||||
_item: PrasiItem;
|
||||
arg: FieldProp;
|
||||
}> = ({ field, fm, PassProp, child, _meta, _item, _sync, arg }) => {
|
||||
}> = ({ field, fm, arg }) => {
|
||||
// return <></>
|
||||
const prefix = typeof field.prefix === "function" ? field.prefix() : typeof field.prefix === "string" ? field.prefix : null;
|
||||
const suffix = typeof field.suffix === "function" ? field.suffix() : typeof field.suffix === "string" ? field.prefix : null;
|
||||
const name = typeof field.name === 'function' ? field.name() : field.name;
|
||||
const name = field.name;
|
||||
const errors = fm.error.get(name);
|
||||
let type_field = typeof arg.type === 'function' ? arg.type() : arg.type; // tipe field
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import { FMLocal, FieldInternal, FieldLocal, FieldProp } from "../typings";
|
||||
|
||||
export const fieldMapping: {
|
||||
[K in FieldProp["type"]]: {
|
||||
id: string;
|
||||
props?:
|
||||
| Record<string, any>
|
||||
| ((
|
||||
fm: FMLocal,
|
||||
field: FieldInternal<K> & {
|
||||
render: () => void;
|
||||
}
|
||||
) => Record<string, any>);
|
||||
};
|
||||
} = {
|
||||
text: { id: "ca7ac237-8f22-4492-bb9d-4b715b1f5c25", props: { type: "text" } },
|
||||
relation: {
|
||||
id: "69263ca0-61a1-4899-ad5f-059ac12b94d1",
|
||||
props: (fm, field) => {
|
||||
const rel = fm.field_def[field.name];
|
||||
if (rel) {
|
||||
if (field.prop && !field.prop.type) {
|
||||
return { type: rel.type };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
switch: { id: ""},
|
||||
};
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
||||
import {mergeRegister} from '@lexical/utils';
|
||||
import {
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
CAN_REDO_COMMAND,
|
||||
CAN_UNDO_COMMAND,
|
||||
FORMAT_ELEMENT_COMMAND,
|
||||
FORMAT_TEXT_COMMAND,
|
||||
REDO_COMMAND,
|
||||
SELECTION_CHANGE_COMMAND,
|
||||
UNDO_COMMAND,
|
||||
} from 'lexical';
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
|
||||
const LowPriority = 1;
|
||||
|
||||
function Divider() {
|
||||
return <div className="divider" />;
|
||||
}
|
||||
|
||||
export default function ToolbarPlugin() {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const toolbarRef = useRef(null);
|
||||
const [canUndo, setCanUndo] = useState(false);
|
||||
const [canRedo, setCanRedo] = useState(false);
|
||||
const [isBold, setIsBold] = useState(false);
|
||||
const [isItalic, setIsItalic] = useState(false);
|
||||
const [isUnderline, setIsUnderline] = useState(false);
|
||||
const [isStrikethrough, setIsStrikethrough] = useState(false);
|
||||
|
||||
const $updateToolbar = useCallback(() => {
|
||||
const selection = $getSelection();
|
||||
if ($isRangeSelection(selection)) {
|
||||
// Update text format
|
||||
setIsBold(selection.hasFormat('bold'));
|
||||
setIsItalic(selection.hasFormat('italic'));
|
||||
setIsUnderline(selection.hasFormat('underline'));
|
||||
setIsStrikethrough(selection.hasFormat('strikethrough'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
editor.registerUpdateListener(({editorState}) => {
|
||||
editorState.read(() => {
|
||||
$updateToolbar();
|
||||
});
|
||||
}),
|
||||
editor.registerCommand(
|
||||
SELECTION_CHANGE_COMMAND,
|
||||
(_payload, _newEditor) => {
|
||||
$updateToolbar();
|
||||
return false;
|
||||
},
|
||||
LowPriority,
|
||||
),
|
||||
editor.registerCommand(
|
||||
CAN_UNDO_COMMAND,
|
||||
(payload) => {
|
||||
setCanUndo(payload);
|
||||
return false;
|
||||
},
|
||||
LowPriority,
|
||||
),
|
||||
editor.registerCommand(
|
||||
CAN_REDO_COMMAND,
|
||||
(payload) => {
|
||||
setCanRedo(payload);
|
||||
return false;
|
||||
},
|
||||
LowPriority,
|
||||
),
|
||||
);
|
||||
}, [editor, $updateToolbar]);
|
||||
|
||||
return (
|
||||
<div className="toolbar" ref={toolbarRef}>
|
||||
<button
|
||||
disabled={!canUndo}
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(UNDO_COMMAND, undefined);
|
||||
}}
|
||||
className="toolbar-item spaced"
|
||||
aria-label="Undo">
|
||||
<i className="format undo" />
|
||||
</button>
|
||||
<button
|
||||
disabled={!canRedo}
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(REDO_COMMAND, undefined);
|
||||
}}
|
||||
className="toolbar-item"
|
||||
aria-label="Redo">
|
||||
<i className="format redo" />
|
||||
</button>
|
||||
<Divider />
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
|
||||
}}
|
||||
className={'toolbar-item spaced ' + (isBold ? 'active' : '')}
|
||||
aria-label="Format Bold">
|
||||
<i className="format bold" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
|
||||
}}
|
||||
className={'toolbar-item spaced ' + (isItalic ? 'active' : '')}
|
||||
aria-label="Format Italics">
|
||||
<i className="format italic" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');
|
||||
}}
|
||||
className={'toolbar-item spaced ' + (isUnderline ? 'active' : '')}
|
||||
aria-label="Format Underline">
|
||||
<i className="format underline" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
|
||||
}}
|
||||
className={'toolbar-item spaced ' + (isStrikethrough ? 'active' : '')}
|
||||
aria-label="Format Strikethrough">
|
||||
<i className="format strikethrough" />
|
||||
</button>
|
||||
<Divider />
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left');
|
||||
}}
|
||||
className="toolbar-item spaced"
|
||||
aria-label="Left Align">
|
||||
<i className="format left-align" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center');
|
||||
}}
|
||||
className="toolbar-item spaced"
|
||||
aria-label="Center Align">
|
||||
<i className="format center-align" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right');
|
||||
}}
|
||||
className="toolbar-item spaced"
|
||||
aria-label="Right Align">
|
||||
<i className="format right-align" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'justify');
|
||||
}}
|
||||
className="toolbar-item"
|
||||
aria-label="Justify Align">
|
||||
<i className="format justify-align" />
|
||||
</button>{' '}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
|
||||
import {TreeView} from '@lexical/react/LexicalTreeView';
|
||||
|
||||
export default function TreeViewPlugin(): JSX.Element {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
return (
|
||||
<TreeView
|
||||
viewClassName="tree-view-output"
|
||||
treeTypeButtonClassName="debug-treetype-button"
|
||||
timeTravelPanelClassName="debug-timetravel-panel"
|
||||
timeTravelButtonClassName="debug-timetravel-button"
|
||||
timeTravelPanelSliderClassName="debug-timetravel-panel-slider"
|
||||
timeTravelPanelButtonClassName="debug-timetravel-panel-button"
|
||||
editor={editor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,451 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #eee;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, '.SFNSText-Regular',
|
||||
sans-serif;
|
||||
font-weight: 500;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.other h2 {
|
||||
font-size: 18px;
|
||||
color: #444;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.other a {
|
||||
color: #777;
|
||||
text-decoration: underline;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.other ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.App {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.ltr {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.rtl {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
margin: 20px auto 20px auto;
|
||||
border-radius: 2px;
|
||||
max-width: 600px;
|
||||
color: #000;
|
||||
position: relative;
|
||||
line-height: 20px;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
}
|
||||
|
||||
.editor-inner {
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.editor-input {
|
||||
min-height: 150px;
|
||||
resize: none;
|
||||
font-size: 15px;
|
||||
caret-color: rgb(5, 5, 5);
|
||||
position: relative;
|
||||
tab-size: 1;
|
||||
outline: 0;
|
||||
padding: 15px 10px;
|
||||
caret-color: #444;
|
||||
}
|
||||
|
||||
.editor-placeholder {
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
text-overflow: ellipsis;
|
||||
top: 15px;
|
||||
left: 10px;
|
||||
font-size: 15px;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.editor-text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.editor-text-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.editor-text-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.editor-text-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.editor-text-underlineStrikethrough {
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
|
||||
.editor-text-code {
|
||||
background-color: rgb(240, 242, 245);
|
||||
padding: 1px 0.25rem;
|
||||
font-family: Menlo, Consolas, Monaco, monospace;
|
||||
font-size: 94%;
|
||||
}
|
||||
|
||||
.editor-link {
|
||||
color: rgb(33, 111, 219);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tree-view-output {
|
||||
display: block;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
margin: 1px auto 10px auto;
|
||||
max-height: 250px;
|
||||
position: relative;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
overflow: auto;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.editor-code {
|
||||
background-color: rgb(240, 242, 245);
|
||||
font-family: Menlo, Consolas, Monaco, monospace;
|
||||
display: block;
|
||||
padding: 8px 8px 8px 52px;
|
||||
line-height: 1.53;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
tab-size: 2;
|
||||
/* white-space: pre; */
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.editor-code:before {
|
||||
content: attr(data-gutter);
|
||||
position: absolute;
|
||||
background-color: #eee;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-right: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
color: #777;
|
||||
white-space: pre-wrap;
|
||||
text-align: right;
|
||||
min-width: 25px;
|
||||
}
|
||||
.editor-code:after {
|
||||
content: attr(data-highlight-language);
|
||||
top: 0;
|
||||
right: 3px;
|
||||
padding: 3px;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.editor-tokenComment {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.editor-tokenPunctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.editor-tokenProperty {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.editor-tokenSelector {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.editor-tokenOperator {
|
||||
color: #9a6e3a;
|
||||
}
|
||||
|
||||
.editor-tokenAttr {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.editor-tokenVariable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.editor-tokenFunction {
|
||||
color: #dd4a68;
|
||||
}
|
||||
|
||||
.editor-paragraph {
|
||||
margin: 0;
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.editor-paragraph:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.editor-heading-h1 {
|
||||
font-size: 24px;
|
||||
color: rgb(5, 5, 5);
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
margin-bottom: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.editor-heading-h2 {
|
||||
font-size: 15px;
|
||||
color: rgb(101, 103, 107);
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.editor-quote {
|
||||
margin: 0;
|
||||
margin-left: 20px;
|
||||
font-size: 15px;
|
||||
color: rgb(101, 103, 107);
|
||||
border-left-color: rgb(206, 208, 212);
|
||||
border-left-width: 4px;
|
||||
border-left-style: solid;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.editor-list-ol {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.editor-list-ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.editor-listitem {
|
||||
margin: 8px 32px 8px 32px;
|
||||
}
|
||||
|
||||
.editor-nested-listitem {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-thumb {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
.debug-timetravel-panel {
|
||||
overflow: hidden;
|
||||
padding: 0 0 10px 0;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.debug-timetravel-panel-slider {
|
||||
padding: 0;
|
||||
flex: 8;
|
||||
}
|
||||
|
||||
.debug-timetravel-panel-button {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
flex: 1;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.debug-timetravel-panel-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.debug-timetravel-button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
position: absolute;
|
||||
background: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.debug-timetravel-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
margin-bottom: 1px;
|
||||
background: #fff;
|
||||
padding: 4px;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.toolbar button.toolbar-item {
|
||||
border: 0;
|
||||
display: flex;
|
||||
background: none;
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.toolbar button.toolbar-item:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.toolbar button.toolbar-item.spaced {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.toolbar button.toolbar-item i.format {
|
||||
background-size: contain;
|
||||
display: inline-block;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-top: 2px;
|
||||
vertical-align: -0.25em;
|
||||
display: flex;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.toolbar button.toolbar-item:disabled i.format {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.toolbar button.toolbar-item.active {
|
||||
background-color: rgba(223, 232, 250, 0.3);
|
||||
}
|
||||
|
||||
.toolbar button.toolbar-item.active i {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.toolbar .toolbar-item:hover:not([disabled]) {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.toolbar .divider {
|
||||
width: 1px;
|
||||
background-color: #eee;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.toolbar .toolbar-item .text {
|
||||
display: flex;
|
||||
line-height: 20px;
|
||||
width: 200px;
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
color: #777;
|
||||
text-overflow: ellipsis;
|
||||
width: 70px;
|
||||
overflow: hidden;
|
||||
height: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.toolbar .toolbar-item .icon {
|
||||
display: flex;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
user-select: none;
|
||||
margin-right: 8px;
|
||||
line-height: 16px;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
i.undo {
|
||||
background-image: url(icons/arrow-counterclockwise.svg);
|
||||
}
|
||||
|
||||
i.redo {
|
||||
background-image: url(icons/arrow-clockwise.svg);
|
||||
}
|
||||
|
||||
i.bold {
|
||||
background-image: url(icons/type-bold.svg);
|
||||
}
|
||||
|
||||
i.italic {
|
||||
background-image: url(icons/type-italic.svg);
|
||||
}
|
||||
|
||||
i.underline {
|
||||
background-image: url(icons/type-underline.svg);
|
||||
}
|
||||
|
||||
i.strikethrough {
|
||||
background-image: url(icons/type-strikethrough.svg);
|
||||
}
|
||||
|
||||
i.left-align {
|
||||
background-image: url(icons/text-left.svg);
|
||||
}
|
||||
|
||||
i.center-align {
|
||||
background-image: url(icons/text-center.svg);
|
||||
}
|
||||
|
||||
i.right-align {
|
||||
background-image: url(icons/text-right.svg);
|
||||
}
|
||||
|
||||
i.justify-align {
|
||||
background-image: url(icons/justify.svg);
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import get from "lodash.get";
|
||||
import { DMMF } from "../../../../../typings/runtime/library";
|
||||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
|
||||
export const FieldCheckbox: FC<{
|
||||
field: FieldLocal;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC } from "react";
|
||||
import { FMLocal, FieldLocal } from "../../typings";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import parser from "any-date-parser";
|
||||
import { format } from "date-fns";
|
||||
import get from "lodash.get";
|
||||
import { argv } from "bun";
|
||||
import { PropTypeText } from "./TypeText";
|
||||
export const FieldMoney: FC<{
|
||||
field: FieldLocal;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { sortTree } from "@/comps/list/utils/sort-tree";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal } from "../../typings";
|
||||
import { OptionItem, RawDropdown } from "../raw/Dropdown";
|
||||
import { FieldLoading } from "../raw/FieldLoading";
|
||||
import { sortTree } from "@/comps/list/utils/sort-tree";
|
||||
|
||||
export type PropTypeRelation = {
|
||||
type: "has-one" | "has-many";
|
||||
|
|
@ -38,7 +38,7 @@ const HasMany: FC<{
|
|||
many: [] as { value: string; label: string }[],
|
||||
pk: "",
|
||||
});
|
||||
const name = typeof field.name === 'string' ? field.name : field.name();
|
||||
const name = field.name;
|
||||
const value = fm.data[name];
|
||||
field.input = input;
|
||||
field.prop = prop;
|
||||
|
|
@ -100,7 +100,7 @@ const HasOne: FC<{
|
|||
list: null as null | any[],
|
||||
pk: "",
|
||||
});
|
||||
const name = typeof field.name === 'string' ? field.name : field.name();
|
||||
const name = field.name;
|
||||
const value = fm.data[name];
|
||||
field.input = input;
|
||||
field.prop = prop;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import Quill from "quill";
|
||||
import "quill/dist/quill.snow.css"; // Import CSS untuk tema Quill
|
||||
import get from "lodash.get";
|
||||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
import { PropTypeText } from "./TypeText";
|
||||
|
||||
export const FieldRichText: FC<{
|
||||
field: FieldLocal;
|
||||
fm: FMLocal;
|
||||
arg: FieldProp;
|
||||
}> = ({ field, fm, arg }) => {
|
||||
prop: PropTypeText;
|
||||
}> = ({ field, fm, prop }) => {
|
||||
const local = useLocal({
|
||||
ref: null as any,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +1,6 @@
|
|||
import { Label } from "@/comps/ui/label";
|
||||
import { Switch } from "@/comps/ui/switch";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button } from "@/comps/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from "@/comps/ui/form";
|
||||
import { FC } from "react";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC } from "react";
|
||||
|
||||
export type PropTypeSwitch = {};
|
||||
export const FieldTypeSwitch: FC<{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import get from "lodash.get";
|
||||
import { FC } from "react";
|
||||
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
|
||||
|
||||
export const FieldTag: FC<{
|
||||
field: FieldLocal;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import get from "lodash.get";
|
||||
import { FC } from "react";
|
||||
import { FMLocal, FieldLocal } from "../../typings";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import parser from "any-date-parser";
|
||||
import { format } from "date-fns";
|
||||
import get from "lodash.get";
|
||||
import { argv } from "bun";
|
||||
import { PropTypeText } from "./TypeText";
|
||||
export const FieldUpload: FC<{
|
||||
field: FieldLocal;
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ export type FMProps = {
|
|||
|
||||
type FieldType = "-" | "relation" | "switch" | "input" | "single-option" | "multi-option";
|
||||
export type FieldProp = {
|
||||
name: string | (() => string);
|
||||
label: string | (() => string);
|
||||
name: string;
|
||||
label: string;
|
||||
desc?: string;
|
||||
props?: any;
|
||||
fm: FMLocal;
|
||||
|
|
@ -60,9 +60,9 @@ export type FMInternal = {
|
|||
error: {
|
||||
readonly object: Record<string, string>;
|
||||
readonly list: { name: string; error: string[] }[];
|
||||
set: (name: string | number | symbol, error: string[]) => void;
|
||||
get: (name: string | number | symbol) => string[];
|
||||
clear: (name?: string | number | symbol) => void;
|
||||
set: (name: string, error: string[]) => void;
|
||||
get: (name: string) => string[];
|
||||
clear: (name?: string) => void;
|
||||
};
|
||||
internal: {
|
||||
reload: {
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
import { newField } from "@/gen/gen_form/new_field";
|
||||
import { FMLocal, FieldLocal } from "../typings";
|
||||
import get from "lodash.get";
|
||||
import { fieldMapping } from "../field/mapping";
|
||||
import { createItem } from "@/gen/utils";
|
||||
|
||||
export const genFieldMitem = (arg: {
|
||||
_meta: any;
|
||||
_item: any;
|
||||
_sync: any;
|
||||
fm: FMLocal;
|
||||
field: FieldLocal;
|
||||
}) => {
|
||||
const { _meta, _item, _sync, fm, field } = arg;
|
||||
const m = _meta[_item.id];
|
||||
if (m) {
|
||||
const mitem = m.mitem;
|
||||
if (mitem) {
|
||||
const childs = mitem
|
||||
.get("component")
|
||||
?.get("props")
|
||||
?.get("child")
|
||||
?.get("content")
|
||||
?.get("childs");
|
||||
let component = fieldMapping[field.type as "text"];
|
||||
|
||||
if (!component) {
|
||||
component = fieldMapping["text"];
|
||||
}
|
||||
|
||||
if (component) {
|
||||
const item = createItem({
|
||||
component: component as any,
|
||||
});
|
||||
|
||||
_sync(childs, [...childs.toJSON(), item]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const updateFieldMItem = (_meta: any, _item: any, _sync: any) => {
|
||||
const m = _meta[_item.id];
|
||||
if (m) {
|
||||
const mitem = m.mitem;
|
||||
if (mitem) {
|
||||
_sync(mitem, _item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -2,7 +2,10 @@ import { useLocal } from "@/utils/use-local";
|
|||
import { useEffect } from "react";
|
||||
import { FieldInternal, FieldProp } from "../typings";
|
||||
|
||||
export const useField = (arg: FieldProp) => {
|
||||
export const useField = (arg: Omit<FieldProp, 'name' | 'label'> & {
|
||||
name: string | (() => string);
|
||||
label: string | (() => string)
|
||||
}) => {
|
||||
const field = useLocal<FieldInternal<typeof arg.type>>({
|
||||
status: "init",
|
||||
Child: () => {
|
||||
|
|
|
|||
|
|
@ -40,15 +40,17 @@ export const MasterDetail: FC<{
|
|||
let isGenerate = false as Boolean;
|
||||
|
||||
try {
|
||||
if (!get(w.md_internal, _item.id)) w.md_internal = {
|
||||
if (!get(w.md_internal, _item.id))
|
||||
w.md_internal = {
|
||||
...w.md_internal,
|
||||
[_item.id]: _item
|
||||
[_item.id]: _item,
|
||||
};
|
||||
isGenerate = false;
|
||||
if (w.generating_prasi_md["master_detail"]) {
|
||||
isGenerate = true;
|
||||
}
|
||||
} catch (ex) { }
|
||||
} catch (ex) {}
|
||||
|
||||
const _ref = useRef({ PassProp, child });
|
||||
const md = useLocal<MDLocalInternal>({
|
||||
name,
|
||||
|
|
@ -80,14 +82,13 @@ export const MasterDetail: FC<{
|
|||
masterDetailApplyParams(md);
|
||||
},
|
||||
},
|
||||
master: { internal: null, render() { } },
|
||||
master: { internal: null, render() {} },
|
||||
panel: {
|
||||
size: 25,
|
||||
min_size: 0,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const local = useLocal({ init: false });
|
||||
if (isEditor) {
|
||||
md.props.mode = mode;
|
||||
|
|
@ -116,7 +117,7 @@ export const MasterDetail: FC<{
|
|||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"c-flex-1 c-flex-col c-flex c-w-full c-h-full c-overflow-hidden",
|
||||
"c-flex-1 c-flex-col c-flex c-w-full c-h-full c-overflow-hidden"
|
||||
)}
|
||||
>
|
||||
{md.props.show_head === "always" && (
|
||||
|
|
@ -127,7 +128,7 @@ export const MasterDetail: FC<{
|
|||
{md.props.mode === "h-split" && <ModeHSplit md={md} mdr={_ref.current} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const ModeFull: FC<{ md: MDLocal; mdr: MDRef }> = ({ md, mdr }) => {
|
||||
if (should_show_tab(md)) {
|
||||
|
|
|
|||
|
|
@ -127,4 +127,8 @@ export const MasterDetailType = `const md = {
|
|||
md?: md;
|
||||
}
|
||||
>;
|
||||
panel: {
|
||||
size: number;
|
||||
min_size: number;
|
||||
};
|
||||
};`;
|
||||
|
|
|
|||
|
|
@ -1,177 +0,0 @@
|
|||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
|
||||
import { cn } from "@/utils";
|
||||
import { Label } from "@/comps/ui/label";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
> = {
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState, formState } = useFormContext();
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
);
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("c-space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
});
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "c-text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("c-text-sm c-text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("c-text-sm c-font-medium c-text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
};
|
||||
|
|
@ -40,6 +40,8 @@ const load_single = async (table: string) => {
|
|||
return single[table];
|
||||
};
|
||||
export const gen_prop_fields = async (gen_table: string) => {
|
||||
if (typeof db === "undefined") return ["- No Database -"];
|
||||
|
||||
const path = window.location.pathname;
|
||||
let id_site = null;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
const cache: any = [];
|
||||
|
||||
export const gen_props_table = async () => {
|
||||
if (typeof db === "undefined") return ["- No Database -"];
|
||||
|
||||
if (cache.length > 0) return cache;
|
||||
const tables = await db._schema.tables();
|
||||
if (!Array.isArray(tables)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue