feat: preview widget settings live
This commit is contained in:
1 parent
d3192688c6
commit
0aa667b8cc
5 files changed
+103
-35
No files matched your search
@@ -59,6 +59,7 @@ export const WidgetPreviewPageContent = ({ kind }: WidgetPreviewPageContentProps
|
||||
preloadWidgetEditModal();
|
||||
try {
|
||||
const integrationData = hasIntegrationSupport ? await utils.integration.all.ensureData() : [];
|
||||
const originalState = state;
|
||||
openWidgetEditModal(
|
||||
{
|
||||
kind,
|
||||
@@ -67,6 +68,8 @@ export const WidgetPreviewPageContent = ({ kind }: WidgetPreviewPageContentProps
|
||||
onSuccessfulEdit: (value) => {
|
||||
setState(value);
|
||||
},
|
||||
onPreviewChange: setState,
|
||||
onPreviewRestore: () => setState(originalState),
|
||||
integrationData: integrationData.filter((integration) =>
|
||||
(currentDefinition.supportedIntegrations ?? []).includes(integration.kind),
|
||||
),
|
||||
|
||||
@@ -30,6 +30,13 @@ interface UpdateItemIntegrations {
|
||||
newIntegrations: string[];
|
||||
}
|
||||
|
||||
interface UpdateItemSettings {
|
||||
itemId: string;
|
||||
options: Record<string, unknown>;
|
||||
advancedOptions: BoardItemAdvancedOptions;
|
||||
integrationIds: string[];
|
||||
}
|
||||
|
||||
export const useItemActions = () => {
|
||||
const { updateBoard } = useUpdateBoard();
|
||||
const currentLayoutId = useCurrentLayout();
|
||||
@@ -77,6 +84,20 @@ export const useItemActions = () => {
|
||||
[updateBoard],
|
||||
);
|
||||
|
||||
const updateItemSettings = useCallback(
|
||||
({ itemId, options, advancedOptions, integrationIds }: UpdateItemSettings) => {
|
||||
updateBoard((previous) => ({
|
||||
...previous,
|
||||
items: previous.items.map((item) => {
|
||||
if (item.id !== itemId || !("integrationIds" in item)) return item;
|
||||
|
||||
return { ...item, options, advancedOptions, integrationIds };
|
||||
}),
|
||||
}));
|
||||
},
|
||||
[updateBoard],
|
||||
);
|
||||
|
||||
const moveAndResizeItem = useCallback(
|
||||
(input: Omit<MoveAndResizeItemInput, "layoutId">) => {
|
||||
updateBoard(moveAndResizeItemCallback({ ...input, layoutId: currentLayoutId }));
|
||||
@@ -105,6 +126,7 @@ export const useItemActions = () => {
|
||||
updateItemOptions,
|
||||
updateItemAdvancedOptions,
|
||||
updateItemIntegrations,
|
||||
updateItemSettings,
|
||||
duplicateItem,
|
||||
createItem,
|
||||
};
|
||||
|
||||
@@ -42,8 +42,7 @@ const BoardItemMenuInner = ({ item, definition, resetErrorBoundary }: BoardItemM
|
||||
const { openModal } = useModalAction(LazyWidgetEditModal);
|
||||
const openMoveModal = useOpenItemMoveModal();
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, duplicateItem, removeItem } =
|
||||
useItemActions();
|
||||
const { updateItemSettings, duplicateItem, removeItem } = useItemActions();
|
||||
const { integrations: integrationData, section } = useSectionContext();
|
||||
const settings = useSettings();
|
||||
const label = item.advancedOptions.title?.trim() || getWidgetName(item.kind, t);
|
||||
@@ -57,6 +56,11 @@ const BoardItemMenuInner = ({ item, definition, resetErrorBoundary }: BoardItemM
|
||||
}, [item, resetErrorBoundary]);
|
||||
|
||||
const openEditModal = () => {
|
||||
const originalSettings = {
|
||||
options: item.options,
|
||||
advancedOptions: item.advancedOptions,
|
||||
integrationIds: item.integrationIds,
|
||||
};
|
||||
openModal(
|
||||
{
|
||||
kind: item.kind,
|
||||
@@ -66,19 +70,14 @@ const BoardItemMenuInner = ({ item, definition, resetErrorBoundary }: BoardItemM
|
||||
options: item.options,
|
||||
integrationIds: item.integrationIds,
|
||||
},
|
||||
onSuccessfulEdit: ({ options, integrationIds, advancedOptions }) => {
|
||||
updateItemOptions({
|
||||
itemId: item.id,
|
||||
newOptions: options,
|
||||
});
|
||||
updateItemAdvancedOptions({
|
||||
itemId: item.id,
|
||||
newAdvancedOptions: advancedOptions,
|
||||
});
|
||||
updateItemIntegrations({
|
||||
itemId: item.id,
|
||||
newIntegrations: integrationIds,
|
||||
});
|
||||
onPreviewChange: (value) => {
|
||||
updateItemSettings({ itemId: item.id, ...value });
|
||||
},
|
||||
onPreviewRestore: () => {
|
||||
updateItemSettings({ itemId: item.id, ...originalSettings });
|
||||
},
|
||||
onSuccessfulEdit: (value) => {
|
||||
updateItemSettings({ itemId: item.id, ...value });
|
||||
refResetErrorBoundaryOnNextRender.current = true;
|
||||
},
|
||||
onIntegrationSaved: resetErrorBoundary,
|
||||
|
||||
@@ -58,7 +58,7 @@ export const WidgetContextMenu = ({
|
||||
const tCommon = useI18n("common.action");
|
||||
const settings = useSettings();
|
||||
const { openModal } = useModalAction(LazyWidgetEditModal);
|
||||
const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations } = useItemActions();
|
||||
const { updateItemSettings } = useItemActions();
|
||||
const hasSupportedIntegrations = (definition.supportedIntegrations?.length ?? 0) > 0;
|
||||
const { data: integrationData = [], isPending } = clientApi.integration.all.useQuery(undefined, {
|
||||
enabled: hasSupportedIntegrations,
|
||||
@@ -154,6 +154,11 @@ export const WidgetContextMenu = ({
|
||||
}) ?? [];
|
||||
|
||||
const openEditModal = useCallback(() => {
|
||||
const originalSettings = {
|
||||
options: item.options,
|
||||
advancedOptions: item.advancedOptions,
|
||||
integrationIds: item.integrationIds,
|
||||
};
|
||||
openModal(
|
||||
{
|
||||
kind: item.kind,
|
||||
@@ -163,10 +168,13 @@ export const WidgetContextMenu = ({
|
||||
options,
|
||||
integrationIds: item.integrationIds,
|
||||
},
|
||||
onPreviewChange: (value) => {
|
||||
updateItemSettings({ itemId: item.id, ...value });
|
||||
},
|
||||
onPreviewRestore: () => {
|
||||
updateItemSettings({ itemId: item.id, ...originalSettings });
|
||||
},
|
||||
onSuccessfulEdit: (editResult) => {
|
||||
updateItemOptions({ itemId: item.id, newOptions: editResult.options });
|
||||
updateItemAdvancedOptions({ itemId: item.id, newAdvancedOptions: editResult.advancedOptions });
|
||||
updateItemIntegrations({ itemId: item.id, newIntegrations: editResult.integrationIds });
|
||||
persistBoard((previous) => ({
|
||||
...previous,
|
||||
items: previous.items.map((boardItem) =>
|
||||
@@ -208,9 +216,7 @@ export const WidgetContextMenu = ({
|
||||
options,
|
||||
persistBoard,
|
||||
settings,
|
||||
updateItemAdvancedOptions,
|
||||
updateItemIntegrations,
|
||||
updateItemOptions,
|
||||
updateItemSettings,
|
||||
]);
|
||||
|
||||
const handleToggle = useCallback(
|
||||
|
||||
@@ -52,6 +52,8 @@ export interface WidgetEditModalProps<TSort extends WidgetKind> {
|
||||
definition: WidgetDefinition;
|
||||
value: WidgetEditModalState;
|
||||
onSuccessfulEdit: (value: WidgetEditModalState) => void;
|
||||
onPreviewChange?: (value: WidgetEditModalState) => void;
|
||||
onPreviewRestore?: () => void;
|
||||
integrationData: IntegrationSelectOption[];
|
||||
integrationSupport: boolean;
|
||||
settings: SettingsContextProps;
|
||||
@@ -84,6 +86,16 @@ export const WidgetEditModal = createModal<WidgetEditModalProps<WidgetKind>>(({
|
||||
const [mountedIntegrationIds, setMountedIntegrationIds] = useState<string[]>([]);
|
||||
const [activeIntegrationId, setActiveIntegrationId] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const hasPreviewedRef = useRef(false);
|
||||
const hasSavedRef = useRef(false);
|
||||
const previewCallbacksRef = useRef({
|
||||
onPreviewChange: innerProps.onPreviewChange,
|
||||
onPreviewRestore: innerProps.onPreviewRestore,
|
||||
});
|
||||
previewCallbacksRef.current = {
|
||||
onPreviewChange: innerProps.onPreviewChange,
|
||||
onPreviewRestore: innerProps.onPreviewRestore,
|
||||
};
|
||||
const widgetFormId = useId();
|
||||
const { focusRect, inspectorSide } = useWidgetEditFocus(innerProps.itemId, innerProps.focusTargetId);
|
||||
|
||||
@@ -117,25 +129,47 @@ export const WidgetEditModal = createModal<WidgetEditModalProps<WidgetKind>>(({
|
||||
if (integrationsRequired) integrationIdsSchema = integrationIdsSchema.min(1);
|
||||
if (Number.isFinite(maxIntegrations)) integrationIdsSchema = integrationIdsSchema.max(maxIntegrations);
|
||||
|
||||
const widgetFormSchema = z.object({
|
||||
options: optionsSchema,
|
||||
integrationIds: integrationIdsSchema,
|
||||
});
|
||||
const applyPreview = (value: WidgetEditModalState) => {
|
||||
if (!innerProps.onPreviewChange) return;
|
||||
|
||||
hasPreviewedRef.current = true;
|
||||
innerProps.onPreviewChange(value);
|
||||
};
|
||||
const form = useForm({
|
||||
mode: "controlled",
|
||||
initialValues: innerProps.value,
|
||||
validate: schemaResolver(
|
||||
z.object({
|
||||
options: optionsSchema,
|
||||
integrationIds: integrationIdsSchema,
|
||||
advancedOptions: z.object({
|
||||
customCssClasses: z.array(z.string()),
|
||||
borderColor: z.string(),
|
||||
}),
|
||||
}),
|
||||
{ sync: true },
|
||||
),
|
||||
initialValues: {
|
||||
options: innerProps.value.options,
|
||||
integrationIds: innerProps.value.integrationIds,
|
||||
},
|
||||
validate: schemaResolver(widgetFormSchema, { sync: true }),
|
||||
validateInputOnBlur: true,
|
||||
validateInputOnChange: true,
|
||||
onValuesChange: (values) => {
|
||||
if (!widgetFormSchema.safeParse(values).success) return;
|
||||
|
||||
applyPreview({ ...values, advancedOptions });
|
||||
},
|
||||
});
|
||||
const { openModal } = useModalAction(WidgetAdvancedOptionsModal);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (!hasPreviewedRef.current || hasSavedRef.current) return;
|
||||
|
||||
const { onPreviewChange, onPreviewRestore } = previewCallbacksRef.current;
|
||||
if (onPreviewRestore) {
|
||||
onPreviewRestore();
|
||||
return;
|
||||
}
|
||||
onPreviewChange?.(innerProps.value);
|
||||
},
|
||||
[innerProps.value],
|
||||
);
|
||||
|
||||
const canModifyApps = session?.user.permissions.includes("app-modify-all") ?? false;
|
||||
const canConfigureWidget = innerProps.kind !== "customApi" || (session?.user.permissions.includes("admin") ?? false);
|
||||
const appId = innerProps.appId;
|
||||
@@ -215,6 +249,7 @@ export const WidgetEditModal = createModal<WidgetEditModalProps<WidgetKind>>(({
|
||||
...values,
|
||||
advancedOptions,
|
||||
});
|
||||
hasSavedRef.current = true;
|
||||
actions.closeModal();
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -403,7 +438,10 @@ export const WidgetEditModal = createModal<WidgetEditModalProps<WidgetKind>>(({
|
||||
onClick={() =>
|
||||
openModal({
|
||||
advancedOptions,
|
||||
onSuccess: setAdvancedOptions,
|
||||
onSuccess: (value) => {
|
||||
setAdvancedOptions(value);
|
||||
applyPreview({ ...form.getValues(), advancedOptions: value });
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user