Skip to content

Commit

Permalink
fix: params render for each component
Browse files Browse the repository at this point in the history
  • Loading branch information
Col0ring committed Dec 23, 2024
1 parent dc92d50 commit 2927b6f
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 121 deletions.
4 changes: 4 additions & 0 deletions frontend/base/slot/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import {
getSetSlotContextFn,
getSetSlotFn,
getSetSlotParamsMappingFnFn,
getSlotContext,
getSlotParams,
setSlotKey,
Expand Down Expand Up @@ -56,6 +57,9 @@
const slotKey = setSlotKey(currentValue);
$: slotKey.set(currentValue);
const slotParamsMappingFn = getSetSlotParamsMappingFnFn(paramsMappingFn);
$: slotParamsMappingFn.set(paramsMappingFn);
const setSlotContext = getSetSlotContextFn({ inherit: true });
$: {
if ($slotParams && $slotParams[currentValue]) {
Expand Down
204 changes: 108 additions & 96 deletions frontend/svelte-preprocess-react/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,17 @@ const skippedGradioProps = [
];
const gradioProps = skippedGradioProps.concat(['attached_events']);

export function getComponentRestProps<T extends Record<string, any>>(
export function mapProps<T extends Record<string, any>>(
props: T,
mapping: Record<keyof T, string> = {} as Record<keyof T, string>
mapping: Record<keyof T, string> = {} as Record<keyof T, string>,
skipGradioProps = false
) {
return mapKeys(omit(props, skippedGradioProps), (_, key) => {
return mapping[key] || convertToCamelCase(key);
});
return mapKeys(
omit(props, skipGradioProps ? [] : skippedGradioProps),
(_, key) => {
return mapping[key] || convertToCamelCase(key);
}
);
}

export function bindEvents<
Expand All @@ -81,105 +85,113 @@ export function bindEvents<
// gradio props
const attachedEvents: string[] = restProps?.attachedEvents || [];

return Array.from(
new Set([
...(Object.keys(internal)
.map((key) => {
const matched = key.match(/bind_(.+)_event/);
if (matched && matched[1]) {
return matched[1];
}
return null;
})
.filter(Boolean) as string[]),
...attachedEvents.map((event) => {
if (eventsMapping && eventsMapping[event]) {
return eventsMapping[event];
}
return event;
}),
])
).reduce(
(acc, event) => {
const splitted = event.split('_');
const handler = (...args: any[]) => {
const payload = args.map((arg) => {
if (
args &&
typeof arg === 'object' &&
(arg.nativeEvent || arg instanceof Event)
) {
return {
type: arg.type,
detail: arg.detail,
timestamp: arg.timeStamp,
clientX: arg.clientX,
clientY: arg.clientY,
targetId: arg.target.id,
targetClassName: arg.target.className,
altKey: arg.altKey,
ctrlKey: arg.ctrlKey,
shiftKey: arg.shiftKey,
metaKey: arg.metaKey,
};
return {
...Array.from(
new Set([
...(Object.keys(internal)
.map((key) => {
const matched = key.match(/bind_(.+)_event/);
if (matched && matched[1]) {
return matched[1];
}
return null;
})
.filter(Boolean) as string[]),
...attachedEvents.map((event) => {
if (eventsMapping && eventsMapping[event]) {
return eventsMapping[event];
}
return arg;
});
let serializedPayload: any;
try {
serializedPayload = JSON.parse(JSON.stringify(payload));
} catch {
serializedPayload = payload.map((item) => {
if (item && typeof item === 'object') {
return Object.fromEntries(
Object.entries(item).filter(([, v]) => {
try {
JSON.stringify(v);
return true;
} catch {
return false;
}
})
);
return event;
}),
])
).reduce(
(acc, event) => {
const splitted = event.split('_');
const handler = (...args: any[]) => {
const payload = args.map((arg) => {
if (
args &&
typeof arg === 'object' &&
(arg.nativeEvent || arg instanceof Event)
) {
return {
type: arg.type,
detail: arg.detail,
timestamp: arg.timeStamp,
clientX: arg.clientX,
clientY: arg.clientY,
targetId: arg.target.id,
targetClassName: arg.target.className,
altKey: arg.altKey,
ctrlKey: arg.ctrlKey,
shiftKey: arg.shiftKey,
metaKey: arg.metaKey,
};
}
return item;
return arg;
});
}
return gradio.dispatch(
event.replace(/[A-Z]/g, (letter) => '_' + letter.toLowerCase()),
{
payload: serializedPayload,
component: {
...component,
...omit(originalRestProps, gradioProps),
},
let serializedPayload: any;
try {
serializedPayload = JSON.parse(JSON.stringify(payload));
} catch {
serializedPayload = payload.map((item) => {
if (item && typeof item === 'object') {
return Object.fromEntries(
Object.entries(item).filter(([, v]) => {
try {
JSON.stringify(v);
return true;
} catch {
return false;
}
})
);
}
return item;
});
}
);
};

if (splitted.length > 1) {
let value: Record<PropertyKey, any> = {
...(component.props[splitted[0]] || restProps?.[splitted[0]] || {}),
return gradio.dispatch(
event.replace(/[A-Z]/g, (letter) => '_' + letter.toLowerCase()),
{
payload: serializedPayload,
component: {
...component,
...omit(originalRestProps, gradioProps),
},
}
);
};
acc[splitted[0]] = value;
for (let i = 1; i < splitted.length - 1; i++) {
const prop = {
...(component.props[splitted[i]] || restProps?.[splitted[i]] || {}),

if (splitted.length > 1) {
let value: Record<PropertyKey, any> = {
...(component.props[splitted[0]] || restProps?.[splitted[0]] || {}),
};
value[splitted[i]] = prop;
value = prop;
acc[splitted[0]] = value;
for (let i = 1; i < splitted.length - 1; i++) {
const prop = {
...(component.props[splitted[i]] ||
restProps?.[splitted[i]] ||
{}),
};
value[splitted[i]] = prop;
value = prop;
}
const listener = splitted[splitted.length - 1];
value[`on${listener.slice(0, 1).toUpperCase()}${listener.slice(1)}`] =
handler;

return acc;
}
const listener = splitted[splitted.length - 1];
value[`on${listener.slice(0, 1).toUpperCase()}${listener.slice(1)}`] =
const listener = splitted[0];
acc[`on${listener.slice(0, 1).toUpperCase()}${listener.slice(1)}`] =
handler;

return acc;
}
const listener = splitted[0];
acc[`on${listener.slice(0, 1).toUpperCase()}${listener.slice(1)}`] =
handler;
return acc;
},
{} as Record<string, any>
),
__render_eventProps: {
props,
eventsMapping,
},
{} as Record<string, any>
);
};
}
27 changes: 26 additions & 1 deletion frontend/svelte-preprocess-react/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createContext, useContext } from 'react';
import React, { createContext, useContext, useMemo, useRef } from 'react';
import { isEqual } from 'lodash-es';

export const FormItemContext = createContext<{
value?: any;
Expand All @@ -14,3 +15,27 @@ export const AutoCompleteContext = createContext<{
}>({});

export const useAutoCompleteContext = () => useContext(AutoCompleteContext);

export const RenderParamsContext = createContext<any[]>([]);

export const RenderParamsProvider: React.FC<{
value: any[];
children: React.ReactNode;
}> = ({ value, children }) => {
const prevValueRef = useRef<typeof value>(value);
return React.createElement(
RenderParamsContext.Provider,
{
value: useMemo(() => {
if (isEqual(prevValueRef.current, value)) {
return prevValueRef.current;
}
prevValueRef.current = value;
return prevValueRef.current;
}, [value]),
},
children
);
};

export const useRenderParamsContext = () => useContext(RenderParamsContext);
68 changes: 63 additions & 5 deletions frontend/svelte-preprocess-react/internal/Bridge.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { bindEvents, mapProps } from '@svelte-preprocess-react/component';
import { ensureObjectCtxValue } from '@svelte-preprocess-react/slot';
import React, { useMemo } from 'react';
import { omitUndefinedProps } from '@utils/omitUndefinedProps';
import { writable } from 'svelte/store';

import { useAutoCompleteContext, useFormItemContext } from '../context';
import {
useAutoCompleteContext,
useFormItemContext,
useRenderParamsContext,
} from '../context';
import useStore, { useStores } from '../useStore';

import Child from './Child';
Expand All @@ -27,6 +33,56 @@ function omitNodeProps(props: Record<string, any>) {
return props;
}

const ContextBridge: React.FC<{
reactComponent: React.ComponentType<any>;
props: Record<string, any>;
children?: React.ReactNode[];
}> = ({ reactComponent, props, children = [] }) => {
const args = useRenderParamsContext();
const {
__render_slotParamsMappingFn: slotParamsMappingFn,
__render_as_item: as_item,
__render_restPropsMapping: restPropsMapping,
__render_eventProps: eventProps,
...rest
} = props || {};
// for render slot like this: (...args) => React.ReactNode
const ctxProps = useMemo(() => {
if (!slotParamsMappingFn) {
return {};
}
let value = slotParamsMappingFn(...args);
if (as_item) {
value = value?.[as_item] || {};
}
value = ensureObjectCtxValue(value);
const restProps = mapProps(value, restPropsMapping, true);
const { __render_eventProps, ...events } = bindEvents(
{
...eventProps.props,
originalRestProps: {
...eventProps.props.originalRestProps,
...restProps,
},
},
eventProps.eventsMapping
);
return {
...restProps,
...events,
};
}, [slotParamsMappingFn, as_item, args, restPropsMapping, eventProps]);

return React.createElement(
reactComponent,
{
...rest,
...ctxProps,
},
...children
);
};

const Bridge: React.FC<BridgeProps> = ({ createPortal, node }) => {
// rerender when target or slot changed
const target = useStore(node.target);
Expand Down Expand Up @@ -98,10 +154,12 @@ const Bridge: React.FC<BridgeProps> = ({ createPortal, node }) => {
}

// render in different container
const element =
children === undefined
? React.createElement(node.reactComponent, props)
: React.createElement(node.reactComponent, props, ...children);
// eslint-disable-next-line react/no-children-prop
const element = React.createElement(ContextBridge, {
props,
reactComponent: node.reactComponent,
children,
});
target._reactElement = element;

return createPortal(element, target);
Expand Down
Loading

0 comments on commit 2927b6f

Please sign in to comment.