mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 12:56:05 +00:00
Merge pull request #253 from bknd-io/feat/ui-readonly-and-fixes
ui: reflect readonly mode by hiding controls + various small styling fixes
This commit is contained in:
@@ -88,7 +88,7 @@ export const Input = forwardRef<HTMLInputElement, React.ComponentProps<"input">>
|
|||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"bg-muted/40 h-11 rounded-md py-2.5 px-4 outline-none w-full",
|
"bg-muted/40 h-11 rounded-md py-2.5 px-4 outline-none w-full disabled:cursor-not-allowed",
|
||||||
disabledOrReadonly && "bg-muted/50 text-primary/50",
|
disabledOrReadonly && "bg-muted/50 text-primary/50",
|
||||||
!disabledOrReadonly &&
|
!disabledOrReadonly &&
|
||||||
"focus:bg-muted focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all",
|
"focus:bg-muted focus:outline-none focus:ring-2 focus:ring-zinc-500 focus:border-transparent transition-all",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ const FieldImpl = ({
|
|||||||
}, [inputProps?.defaultValue]);
|
}, [inputProps?.defaultValue]);
|
||||||
|
|
||||||
const disabled = firstDefined(
|
const disabled = firstDefined(
|
||||||
|
ctx.readOnly,
|
||||||
inputProps?.disabled,
|
inputProps?.disabled,
|
||||||
props.disabled,
|
props.disabled,
|
||||||
schema.readOnly,
|
schema.readOnly,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export type FormContext<Data> = {
|
|||||||
options: FormOptions;
|
options: FormOptions;
|
||||||
root: string;
|
root: string;
|
||||||
_formStateAtom: PrimitiveAtom<FormState<Data>>;
|
_formStateAtom: PrimitiveAtom<FormState<Data>>;
|
||||||
|
readOnly: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormContext = createContext<FormContext<any>>(undefined!);
|
const FormContext = createContext<FormContext<any>>(undefined!);
|
||||||
@@ -81,6 +82,7 @@ export function Form<
|
|||||||
hiddenSubmit = true,
|
hiddenSubmit = true,
|
||||||
ignoreKeys = [],
|
ignoreKeys = [],
|
||||||
options = {},
|
options = {},
|
||||||
|
readOnly = false,
|
||||||
...props
|
...props
|
||||||
}: Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit"> & {
|
}: Omit<ComponentPropsWithoutRef<"form">, "onChange" | "onSubmit"> & {
|
||||||
schema: Schema;
|
schema: Schema;
|
||||||
@@ -93,6 +95,7 @@ export function Form<
|
|||||||
hiddenSubmit?: boolean;
|
hiddenSubmit?: boolean;
|
||||||
options?: FormOptions;
|
options?: FormOptions;
|
||||||
initialValues?: Schema extends JSONSchema ? FromSchema<Schema> : never;
|
initialValues?: Schema extends JSONSchema ? FromSchema<Schema> : never;
|
||||||
|
readOnly?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [schema, initial] = omitSchema(_schema, ignoreKeys, _initialValues);
|
const [schema, initial] = omitSchema(_schema, ignoreKeys, _initialValues);
|
||||||
const lib = useMemo(() => new Draft2019(schema), [JSON.stringify(schema)]);
|
const lib = useMemo(() => new Draft2019(schema), [JSON.stringify(schema)]);
|
||||||
@@ -190,8 +193,9 @@ export function Form<
|
|||||||
options,
|
options,
|
||||||
root: "",
|
root: "",
|
||||||
path: "",
|
path: "",
|
||||||
|
readOnly,
|
||||||
}),
|
}),
|
||||||
[schema, initialValues, options],
|
[schema, initialValues, options, readOnly],
|
||||||
) as any;
|
) as any;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -339,15 +339,15 @@ export const SectionHeaderLink = <E extends React.ElementType = "a">({
|
|||||||
<Tag
|
<Tag
|
||||||
{...props}
|
{...props}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"hover:bg-primary/5 flex flex-row items-center justify-center gap-2.5 px-5 h-12 leading-none font-medium text-primary/80 rounded-tr-lg rounded-tl-lg",
|
"flex flex-row items-center justify-center gap-2.5 px-5 h-12 leading-none font-medium text-primary/80 rounded-tr-lg rounded-tl-lg cursor-pointer z-2",
|
||||||
active
|
active
|
||||||
? "bg-background hover:bg-background text-primary border border-muted border-b-0"
|
? "bg-primary/3 text-primary border border-muted border-b-0"
|
||||||
: "link",
|
: "link hover:bg-primary/2",
|
||||||
badge && "pr-4",
|
badge && "pr-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
<span className="truncate">{children}</span>
|
||||||
{badge ? (
|
{badge ? (
|
||||||
<span className="px-3 py-1 rounded-full font-mono bg-primary/5 text-sm leading-none">
|
<span className="px-3 py-1 rounded-full font-mono bg-primary/5 text-sm leading-none">
|
||||||
{badge}
|
{badge}
|
||||||
@@ -365,8 +365,8 @@ export type SectionHeaderTabsProps = {
|
|||||||
};
|
};
|
||||||
export const SectionHeaderTabs = ({ title, items }: SectionHeaderTabsProps) => {
|
export const SectionHeaderTabs = ({ title, items }: SectionHeaderTabsProps) => {
|
||||||
return (
|
return (
|
||||||
<SectionHeader className="mt-10 border-t pl-3 pb-0 items-end">
|
<SectionHeader className="mt-10 border-t border-t-muted pl-3 pb-0 items-end overflow-x-scroll app-scrollbar relative after:inset-0 after:z-1">
|
||||||
<div className="flex flex-row items-center gap-6 -mb-px">
|
<div className="flex flex-row items-center gap-6 relative">
|
||||||
{title && (
|
{title && (
|
||||||
<SectionHeaderTitle className="pl-2 hidden md:block">{title}</SectionHeaderTitle>
|
<SectionHeaderTitle className="pl-2 hidden md:block">{title}</SectionHeaderTitle>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ const PopoverTable = ({
|
|||||||
data={container ?? []}
|
data={container ?? []}
|
||||||
entity={entity}
|
entity={entity}
|
||||||
select={query.select}
|
select={query.select}
|
||||||
total={container.meta?.count}
|
total={container.body.meta?.count}
|
||||||
page={query.page}
|
page={query.page}
|
||||||
onClickRow={onClickRow}
|
onClickRow={onClickRow}
|
||||||
onClickPage={onClickPage}
|
onClickPage={onClickPage}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import { s } from "bknd/utils";
|
|||||||
import { cloneSchema } from "core/utils/schema";
|
import { cloneSchema } from "core/utils/schema";
|
||||||
|
|
||||||
const schema = s.object({
|
const schema = s.object({
|
||||||
name: entitySchema.properties.name,
|
name: s.string({
|
||||||
|
pattern: /^[a-z][a-zA-Z_]+$/,
|
||||||
|
}),
|
||||||
config: entitySchema.properties.config.partial().optional(),
|
config: entitySchema.properties.config.partial().optional(),
|
||||||
});
|
});
|
||||||
type Schema = s.Static<typeof schema>;
|
type Schema = s.Static<typeof schema>;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ function AuthRolesEditInternal({ params }) {
|
|||||||
const roleName = params.role;
|
const roleName = params.role;
|
||||||
const role = config.roles?.[roleName];
|
const role = config.roles?.[roleName];
|
||||||
const formRef = useRef<AuthRoleFormRef>(null);
|
const formRef = useRef<AuthRoleFormRef>(null);
|
||||||
|
const { readonly } = useBknd();
|
||||||
|
|
||||||
async function handleUpdate() {
|
async function handleUpdate() {
|
||||||
console.log("data", formRef.current?.isValid());
|
console.log("data", formRef.current?.isValid());
|
||||||
@@ -57,7 +58,7 @@ function AuthRolesEditInternal({ params }) {
|
|||||||
absolute: true,
|
absolute: true,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
!readonly && {
|
||||||
label: "Delete",
|
label: "Delete",
|
||||||
onClick: handleDelete,
|
onClick: handleDelete,
|
||||||
destructive: true,
|
destructive: true,
|
||||||
@@ -67,9 +68,11 @@ function AuthRolesEditInternal({ params }) {
|
|||||||
>
|
>
|
||||||
<IconButton Icon={TbDots} />
|
<IconButton Icon={TbDots} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
!readonly && (
|
||||||
<Button variant="primary" onClick={handleUpdate}>
|
<Button variant="primary" onClick={handleUpdate}>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
className="pl-3"
|
className="pl-3"
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import { Button } from "../../components/buttons/Button";
|
|||||||
import { CellValue, DataTable } from "../../components/table/DataTable";
|
import { CellValue, DataTable } from "../../components/table/DataTable";
|
||||||
import * as AppShell from "../../layouts/AppShell/AppShell";
|
import * as AppShell from "../../layouts/AppShell/AppShell";
|
||||||
import { routes, useNavigate } from "../../lib/routes";
|
import { routes, useNavigate } from "../../lib/routes";
|
||||||
|
import { useBknd } from "ui/client/bknd";
|
||||||
|
|
||||||
export function AuthRolesList() {
|
export function AuthRolesList() {
|
||||||
const [navigate] = useNavigate();
|
const [navigate] = useNavigate();
|
||||||
const { config, actions } = useBkndAuth();
|
const { config, actions } = useBkndAuth();
|
||||||
|
const { readonly } = useBknd();
|
||||||
|
|
||||||
const data = Object.values(
|
const data = Object.values(
|
||||||
transformObject(config.roles ?? {}, (role, name) => ({
|
transformObject(config.roles ?? {}, (role, name) => ({
|
||||||
@@ -30,6 +32,7 @@ export function AuthRolesList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openCreateModal() {
|
function openCreateModal() {
|
||||||
|
if (readonly) return;
|
||||||
bkndModals.open(
|
bkndModals.open(
|
||||||
"form",
|
"form",
|
||||||
{
|
{
|
||||||
@@ -59,9 +62,11 @@ export function AuthRolesList() {
|
|||||||
<>
|
<>
|
||||||
<AppShell.SectionHeader
|
<AppShell.SectionHeader
|
||||||
right={
|
right={
|
||||||
|
!readonly && (
|
||||||
<Button variant="primary" onClick={openCreateModal}>
|
<Button variant="primary" onClick={openCreateModal}>
|
||||||
Create new
|
Create new
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Roles & Permissions
|
Roles & Permissions
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const formConfig = {
|
|||||||
|
|
||||||
function AuthSettingsInternal() {
|
function AuthSettingsInternal() {
|
||||||
const { config, schema: _schema, actions, $auth } = useBkndAuth();
|
const { config, schema: _schema, actions, $auth } = useBkndAuth();
|
||||||
|
const { readonly } = useBknd();
|
||||||
const schema = JSON.parse(JSON.stringify(_schema));
|
const schema = JSON.parse(JSON.stringify(_schema));
|
||||||
|
|
||||||
schema.properties.jwt.required = ["alg"];
|
schema.properties.jwt.required = ["alg"];
|
||||||
@@ -61,7 +62,13 @@ function AuthSettingsInternal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form schema={schema} initialValues={config as any} onSubmit={onSubmit} {...formConfig}>
|
<Form
|
||||||
|
schema={schema}
|
||||||
|
initialValues={config as any}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
{...formConfig}
|
||||||
|
readOnly={readonly}
|
||||||
|
>
|
||||||
<Subscribe
|
<Subscribe
|
||||||
selector={(state) => ({
|
selector={(state) => ({
|
||||||
dirty: state.dirty,
|
dirty: state.dirty,
|
||||||
@@ -73,6 +80,7 @@ function AuthSettingsInternal() {
|
|||||||
<AppShell.SectionHeader
|
<AppShell.SectionHeader
|
||||||
className="pl-4"
|
className="pl-4"
|
||||||
right={
|
right={
|
||||||
|
!readonly && (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -80,6 +88,7 @@ function AuthSettingsInternal() {
|
|||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ const formOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function AuthStrategiesListInternal() {
|
function AuthStrategiesListInternal() {
|
||||||
|
const { readonly } = useBknd();
|
||||||
const $auth = useBkndAuth();
|
const $auth = useBkndAuth();
|
||||||
const config = $auth.config.strategies;
|
const config = $auth.config.strategies;
|
||||||
const schema = $auth.schema.properties.strategies;
|
const schema = $auth.schema.properties.strategies;
|
||||||
@@ -80,6 +81,7 @@ function AuthStrategiesListInternal() {
|
|||||||
initialValues={config}
|
initialValues={config}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
options={formOptions}
|
options={formOptions}
|
||||||
|
readOnly={readonly}
|
||||||
>
|
>
|
||||||
<Subscribe
|
<Subscribe
|
||||||
selector={(state) => ({
|
selector={(state) => ({
|
||||||
@@ -92,6 +94,7 @@ function AuthStrategiesListInternal() {
|
|||||||
<AppShell.SectionHeader
|
<AppShell.SectionHeader
|
||||||
className="pl-4"
|
className="pl-4"
|
||||||
right={
|
right={
|
||||||
|
!readonly && (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -99,6 +102,7 @@ function AuthStrategiesListInternal() {
|
|||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Strategies
|
Strategies
|
||||||
|
|||||||
@@ -112,9 +112,15 @@ const EntityLinkList = ({
|
|||||||
suggestCreate = false,
|
suggestCreate = false,
|
||||||
}: { entities: Entity[]; title?: string; context: "data" | "schema"; suggestCreate?: boolean }) => {
|
}: { entities: Entity[]; title?: string; context: "data" | "schema"; suggestCreate?: boolean }) => {
|
||||||
const { $data } = useBkndData();
|
const { $data } = useBkndData();
|
||||||
|
const { readonly } = useBknd();
|
||||||
const navigate = useRouteNavigate();
|
const navigate = useRouteNavigate();
|
||||||
|
|
||||||
if (entities.length === 0) {
|
if (entities.length === 0) {
|
||||||
return suggestCreate ? (
|
if (suggestCreate) {
|
||||||
|
if (readonly) {
|
||||||
|
return <Empty className="py-10" description="No entities created." />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
<Empty
|
<Empty
|
||||||
className="py-10"
|
className="py-10"
|
||||||
description="Create your first entity to get started."
|
description="Create your first entity to get started."
|
||||||
@@ -123,7 +129,10 @@ const EntityLinkList = ({
|
|||||||
onClick: () => $data.modals.createEntity(),
|
onClick: () => $data.modals.createEntity(),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null;
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick(entity: Entity) {
|
function handleClick(entity: Entity) {
|
||||||
@@ -163,7 +172,7 @@ const EntityLinkList = ({
|
|||||||
href={href}
|
href={href}
|
||||||
className="justify-between items-center"
|
className="justify-between items-center"
|
||||||
>
|
>
|
||||||
{entity.label}
|
<span className="truncate">{entity.label}</span>
|
||||||
|
|
||||||
{isLinkActive(href, 1) && (
|
{isLinkActive(href, 1) && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { bkndModals } from "ui/modals";
|
|||||||
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
||||||
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
||||||
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
export function DataEntityUpdate({ params }) {
|
export function DataEntityUpdate({ params }) {
|
||||||
return <DataEntityUpdateImpl params={params} key={params.entity} />;
|
return <DataEntityUpdateImpl params={params} key={params.entity} />;
|
||||||
@@ -58,14 +59,22 @@ function DataEntityUpdateImpl({ params }) {
|
|||||||
async function onSubmitted(changeSet?: EntityData) {
|
async function onSubmitted(changeSet?: EntityData) {
|
||||||
//return;
|
//return;
|
||||||
if (!changeSet) {
|
if (!changeSet) {
|
||||||
goBack();
|
notifications.show({
|
||||||
|
title: `Updating ${entity?.label}`,
|
||||||
|
message: "No changes to update",
|
||||||
|
color: "yellow",
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await $q.update(changeSet);
|
await $q.update(changeSet);
|
||||||
if (error) setError(null);
|
if (error) setError(null);
|
||||||
goBack();
|
notifications.show({
|
||||||
|
title: `Updating ${entity?.label}`,
|
||||||
|
message: `Successfully updated ID ${entityId}`,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : "Failed to update");
|
setError(e instanceof Error ? e.message : "Failed to update");
|
||||||
}
|
}
|
||||||
@@ -76,6 +85,11 @@ function DataEntityUpdateImpl({ params }) {
|
|||||||
try {
|
try {
|
||||||
await $q._delete();
|
await $q._delete();
|
||||||
if (error) setError(null);
|
if (error) setError(null);
|
||||||
|
notifications.show({
|
||||||
|
title: `Deleting ${entity?.label}`,
|
||||||
|
message: `Successfully deleted ID ${entityId}`,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
goBack();
|
goBack();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : "Failed to delete");
|
setError(e instanceof Error ? e.message : "Failed to delete");
|
||||||
@@ -233,7 +247,7 @@ function EntityDetailRelations({
|
|||||||
return {
|
return {
|
||||||
as: "button",
|
as: "button",
|
||||||
type: "button",
|
type: "button",
|
||||||
label: ucFirst(other.reference),
|
label: ucFirst(other.entity.label),
|
||||||
onClick: () => handleClick(relation),
|
onClick: () => handleClick(relation),
|
||||||
active: selected?.other(entity).reference === other.reference,
|
active: selected?.other(entity).reference === other.reference,
|
||||||
badge: relation.type(),
|
badge: relation.type(),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { routes, useNavigate } from "ui/lib/routes";
|
|||||||
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
||||||
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
||||||
import { s } from "bknd/utils";
|
import { s } from "bknd/utils";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
|
||||||
export function DataEntityCreate({ params }) {
|
export function DataEntityCreate({ params }) {
|
||||||
const { $data } = useBkndData();
|
const { $data } = useBkndData();
|
||||||
@@ -39,10 +40,18 @@ export function DataEntityCreate({ params }) {
|
|||||||
if (!changeSet) return;
|
if (!changeSet) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await $q.create(changeSet);
|
const result = await $q.create(changeSet);
|
||||||
if (error) setError(null);
|
if (error) setError(null);
|
||||||
// @todo: navigate to created?
|
if (result.id) {
|
||||||
|
notifications.show({
|
||||||
|
title: `Creating ${entity?.label}`,
|
||||||
|
message: `Successfully created with ID ${result.id}`,
|
||||||
|
color: "green",
|
||||||
|
});
|
||||||
|
navigate(routes.data.entity.edit(params.entity, result.id));
|
||||||
|
} else {
|
||||||
goBack();
|
goBack();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : "Failed to create");
|
setError(e instanceof Error ? e.message : "Failed to create");
|
||||||
}
|
}
|
||||||
@@ -79,8 +88,12 @@ export function DataEntityCreate({ params }) {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
className="pl-3"
|
||||||
>
|
>
|
||||||
<Breadcrumbs2 backTo={backHref} path={[{ label: entity.label }, { label: "Create" }]} />
|
<Breadcrumbs2
|
||||||
|
backTo={backHref}
|
||||||
|
path={[{ label: entity.label, href: backHref }, { label: "Create" }]}
|
||||||
|
/>
|
||||||
</AppShell.SectionHeader>
|
</AppShell.SectionHeader>
|
||||||
<AppShell.Scrollable key={entity.name}>
|
<AppShell.Scrollable key={entity.name}>
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ function EntityCreateButton({ entity }: { entity: Entity }) {
|
|||||||
media: b.app.config.media.entity_name,
|
media: b.app.config.media.entity_name,
|
||||||
};
|
};
|
||||||
if (system.users === entity.name) {
|
if (system.users === entity.name) {
|
||||||
|
if (b.readonly) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={createUserModal.open} variant="primary">
|
<Button onClick={createUserModal.open} variant="primary">
|
||||||
New User
|
New User
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const formConfig = {
|
|||||||
|
|
||||||
function MediaSettingsInternal() {
|
function MediaSettingsInternal() {
|
||||||
const { config, schema: _schema, actions } = useBkndMedia();
|
const { config, schema: _schema, actions } = useBkndMedia();
|
||||||
|
const { readonly } = useBknd();
|
||||||
const schema = JSON.parse(JSON.stringify(_schema));
|
const schema = JSON.parse(JSON.stringify(_schema));
|
||||||
|
|
||||||
schema.if = { properties: { enabled: { const: true } } };
|
schema.if = { properties: { enabled: { const: true } } };
|
||||||
@@ -53,7 +54,13 @@ function MediaSettingsInternal() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form schema={schema} initialValues={config as any} onSubmit={onSubmit} {...formConfig}>
|
<Form
|
||||||
|
schema={schema}
|
||||||
|
initialValues={config as any}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
{...formConfig}
|
||||||
|
readOnly={readonly}
|
||||||
|
>
|
||||||
<Subscribe
|
<Subscribe
|
||||||
selector={(state) => ({
|
selector={(state) => ({
|
||||||
dirty: state.dirty,
|
dirty: state.dirty,
|
||||||
@@ -64,6 +71,7 @@ function MediaSettingsInternal() {
|
|||||||
{({ dirty, errors, submitting }) => (
|
{({ dirty, errors, submitting }) => (
|
||||||
<AppShell.SectionHeader
|
<AppShell.SectionHeader
|
||||||
right={
|
right={
|
||||||
|
!readonly && (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -71,6 +79,7 @@ function MediaSettingsInternal() {
|
|||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
@@ -132,6 +141,7 @@ const AdapterIcon = ({ type }: { type: string }) => {
|
|||||||
|
|
||||||
function Adapters() {
|
function Adapters() {
|
||||||
const ctx = AnyOf.useContext();
|
const ctx = AnyOf.useContext();
|
||||||
|
const { readonly } = useBknd();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formy.Group>
|
<Formy.Group>
|
||||||
@@ -150,6 +160,7 @@ function Adapters() {
|
|||||||
"flex flex-row items-center justify-center gap-3 border",
|
"flex flex-row items-center justify-center gap-3 border",
|
||||||
ctx.selected === i && "border-primary",
|
ctx.selected === i && "border-primary",
|
||||||
)}
|
)}
|
||||||
|
disabled={readonly}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<AdapterIcon type={schema.properties.type.const} />
|
<AdapterIcon type={schema.properties.type.const} />
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ export function Setting<Schema extends s.ObjectSchema = s.ObjectSchema>({
|
|||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteAllowed = options?.allowDelete?.(config) ?? true;
|
const deleteAllowed = (options?.allowDelete?.(config) ?? true) && !readonly;
|
||||||
const editAllowed = options?.allowEdit?.(config) ?? true;
|
const editAllowed = (options?.allowEdit?.(config) ?? true) && !readonly;
|
||||||
const showAlert = options?.showAlert?.(config) ?? undefined;
|
const showAlert = options?.showAlert?.(config) ?? undefined;
|
||||||
|
|
||||||
console.log("--setting", { schema, config, prefix, path, exclude });
|
console.log("--setting", { schema, config, prefix, path, exclude });
|
||||||
@@ -120,14 +120,14 @@ export function Setting<Schema extends s.ObjectSchema = s.ObjectSchema>({
|
|||||||
extractedKeys.find((key) => window.location.pathname.endsWith(key)) ?? extractedKeys[0];
|
extractedKeys.find((key) => window.location.pathname.endsWith(key)) ?? extractedKeys[0];
|
||||||
|
|
||||||
const onToggleEdit = useEvent(() => {
|
const onToggleEdit = useEvent(() => {
|
||||||
if (!editAllowed || readonly) return;
|
if (!editAllowed) return;
|
||||||
|
|
||||||
setEditing((prev) => !prev);
|
setEditing((prev) => !prev);
|
||||||
//formRef.current?.cancel();
|
//formRef.current?.cancel();
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSave = useEvent(async () => {
|
const onSave = useEvent(async () => {
|
||||||
if (!editAllowed || !editing || readonly) return;
|
if (!editAllowed || !editing) return;
|
||||||
|
|
||||||
if (formRef.current?.validateForm()) {
|
if (formRef.current?.validateForm()) {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
@@ -215,14 +215,14 @@ export function Setting<Schema extends s.ObjectSchema = s.ObjectSchema>({
|
|||||||
>
|
>
|
||||||
<IconButton Icon={TbSettings} />
|
<IconButton Icon={TbSettings} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Button onClick={onToggleEdit} disabled={!editAllowed || readonly}>
|
<Button onClick={onToggleEdit} disabled={!editAllowed}>
|
||||||
{editing ? "Cancel" : "Edit"}
|
{editing ? "Cancel" : "Edit"}
|
||||||
</Button>
|
</Button>
|
||||||
{editing && (
|
{editing && (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
disabled={submitting || !editAllowed || readonly}
|
disabled={submitting || !editAllowed}
|
||||||
>
|
>
|
||||||
{submitting ? "Save..." : "Save"}
|
{submitting ? "Save..." : "Save"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const SettingNewModal = ({
|
|||||||
const [location, navigate] = useLocation();
|
const [location, navigate] = useLocation();
|
||||||
const [formSchema, setFormSchema] = useState(schema);
|
const [formSchema, setFormSchema] = useState(schema);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const { actions } = useBknd();
|
const { actions, readonly } = useBknd();
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const isGeneratedKey = generateKey !== undefined;
|
const isGeneratedKey = generateKey !== undefined;
|
||||||
const isStaticGeneratedKey = typeof generateKey === "string";
|
const isStaticGeneratedKey = typeof generateKey === "string";
|
||||||
@@ -98,6 +98,7 @@ export const SettingNewModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{!readonly && (
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
{isAnyOf ? (
|
{isAnyOf ? (
|
||||||
<Dropdown position="top-start" items={anyOfItems} itemsClassName="gap-3">
|
<Dropdown position="top-start" items={anyOfItems} itemsClassName="gap-3">
|
||||||
@@ -107,6 +108,7 @@ export const SettingNewModal = ({
|
|||||||
<Button onClick={open}>Add new</Button>
|
<Button onClick={open}>Add new</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={opened}
|
open={opened}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const DataSettings = ({
|
|||||||
schema,
|
schema,
|
||||||
config,
|
config,
|
||||||
}: { schema: ModuleSchemas["data"]; config: ModuleConfigs["data"] }) => {
|
}: { schema: ModuleSchemas["data"]; config: ModuleConfigs["data"] }) => {
|
||||||
const { app } = useBknd();
|
const { app, readonly } = useBknd();
|
||||||
const prefix = app.getAbsolutePath("settings");
|
const prefix = app.getAbsolutePath("settings");
|
||||||
const entities = Object.keys(config.entities ?? {});
|
const entities = Object.keys(config.entities ?? {});
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ export const DataSettings = ({
|
|||||||
options={{
|
options={{
|
||||||
showAlert: (config: any) => {
|
showAlert: (config: any) => {
|
||||||
// it's weird, but after creation, the config is not set (?)
|
// it's weird, but after creation, the config is not set (?)
|
||||||
if (config?.type === "primary") {
|
if (config?.type === "primary" && !readonly) {
|
||||||
return "Modifying the primary field may result in strange behaviors.";
|
return "Modifying the primary field may result in strange behaviors.";
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -137,7 +137,7 @@ export const DataSettings = ({
|
|||||||
config={config.entities?.[entity] as any}
|
config={config.entities?.[entity] as any}
|
||||||
options={{
|
options={{
|
||||||
showAlert: (config: any) => {
|
showAlert: (config: any) => {
|
||||||
if (config.type === "system") {
|
if (config.type === "system" && !readonly) {
|
||||||
return "Modifying the system entities may result in strange behaviors.";
|
return "Modifying the system entities may result in strange behaviors.";
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user