Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { FC, PropsWithChildren, useMemo, useState } from 'react';
import { ShaForm } from '@/providers/form';
import { Button, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { useFormDesigner, useFormDesignerSelectedComponentId, useFormDesignerIsDebug } from '@/providers/formDesigner';
import { DeleteFilled, FunctionOutlined } from '@ant-design/icons';
import { FunctionOutlined } from '@ant-design/icons';
import { useStyles } from '../styles/styles';

interface IDragWrapperProps {
Expand All @@ -15,7 +15,7 @@ export const DragWrapper: FC<PropsWithChildren<IDragWrapperProps>> = (props) =>

const selectedComponentId = useFormDesignerSelectedComponentId();
const isDebug = useFormDesignerIsDebug();
const { setSelectedComponent, deleteComponent } = useFormDesigner();
const { setSelectedComponent } = useFormDesigner();
const [isOpen, setIsOpen] = useState(false);

const componentModel = ShaForm.useComponentModel(props.componentId);
Expand Down Expand Up @@ -61,18 +61,9 @@ export const DragWrapper: FC<PropsWithChildren<IDragWrapperProps>> = (props) =>
event.stopPropagation();
setIsOpen(false);
};
const onDeleteClick = (): void => {
deleteComponent({ componentId: componentModel.id });
};

return (
<div className={styles.componentDragHandle} onClick={onClick} onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
{!props?.readOnly && isOpen && (
<div className={styles.shaComponentControls}>
<Button icon={<DeleteFilled color="red" />} onClick={onDeleteClick} size="small" danger />
</div>
)}

<Tooltip title={tooltip} placement="right" open={isOpen}>
{props.children}
</Tooltip>
Expand Down
272 changes: 155 additions & 117 deletions shesha-reactjs/src/components/formDesigner/designerMainArea/index.tsx
Original file line number Diff line number Diff line change
@@ -1,117 +1,155 @@
import ConditionalWrap from '@/components/conditionalWrapper';
import { DataContextProvider, MetadataProvider, useDataContextManager, useShaFormInstance } from '@/providers';
import { useFormDesignerFormMode, useFormDesignerIsDebug, useFormDesignerMarkup, useFormDesignerReadOnly, useFormDesignerSettings } from '@/providers/formDesigner';
import ParentProvider from '@/providers/parentProvider';
import React, { FC, useMemo, useEffect } from 'react';
import { ComponentPropertiesPanel } from '../componentPropertiesPanel';
import { ComponentPropertiesTitle } from '../componentPropertiesTitle';
import { DebugPanel } from '../debugPanel';
import { useStyles } from '../styles/styles';
import Toolbox from '../toolbox';
import { SheshaCommonContexts } from '@/providers/dataContextManager/models';
import { IViewType } from '@/providers/canvas/contexts';
import { SidebarContainer } from '@/components/sidebarContainer';
import { ConfigurableFormRenderer } from '@/components/configurableForm/configurableFormRenderer';

const rightSidebarProps = {
title: () => <ComponentPropertiesTitle />,
content: () => <ComponentPropertiesPanel />,
placeholder: 'Properties',
};

export const DesignerMainArea: FC<{ viewType?: IViewType }> = ({ viewType = 'configStudio' }) => {
const isDebug = useFormDesignerIsDebug();
const readOnly = useFormDesignerReadOnly();
const formSettings = useFormDesignerSettings();
const formMode = useFormDesignerFormMode();
const shaForm = useShaFormInstance();
const { antdForm: form } = shaForm;
const { styles } = useStyles();

const showMarkup = false;
const markup = useFormDesignerMarkup();

const noPageContext = !Boolean(useDataContextManager().getPageContext());

useEffect(() => {
if (shaForm) {
shaForm.applyMarkupAsync({
formFlatMarkup: shaForm.flatStructure,
formSettings: formSettings,
});
}
}, [formSettings, shaForm]);

const leftSidebarProps = useMemo(() =>
readOnly ? null : { title: 'Builder Components', content: () => <Toolbox />, placeholder: 'Builder Components' },
[readOnly]);

return (
<div
className={styles.mainArea}
style={{
borderTop: '1px solid #d3d3d3',
...(formMode !== 'designer' && {
maxHeight: '85vh',
overflow: 'auto',
}),
}}
>
<ConditionalWrap
condition={formMode === 'designer'}
wrap={(children) => (
<SidebarContainer
leftSidebarProps={leftSidebarProps}
rightSidebarProps={rightSidebarProps}
canZoom={true}
viewType={viewType}
>
{children}
</SidebarContainer>
)}
>
<ConditionalWrap
condition={Boolean(formSettings?.modelType)}
wrap={(children) => (<MetadataProvider modelType={formSettings?.modelType}>{children}</MetadataProvider>)}
>
{/* Use special format of parent properties to avoid adding form context */}
<ParentProvider model={null} formMode="designer" name="designer" isScope addContext={false}>
{/* pageContext has added only to customize the designed form. It is not used as a data context.*/}
{/* formContext has added only to customize the designed form. It is not used as a data context.*/}
<ConditionalWrap
condition={noPageContext}
wrap={(children) => (
<DataContextProvider
id="pageContext"
description="Designer Page context"
name={SheshaCommonContexts.PageContext}
type="page"
webStorageType="sessionStorage"
>
<DataContextProvider
id="formContext"
description="Designer Form context"
name={SheshaCommonContexts.FormContext}
type="form"
webStorageType="sessionStorage"
>
{children}
</DataContextProvider>
</DataContextProvider>
)}
>

{showMarkup && <textarea readOnly value={JSON.stringify(markup, null, 2)} /> /* ToDo: AS - remove after inheritance implementation */}
<ConfigurableFormRenderer form={form} className={formMode === 'designer' ? styles.designerWorkArea : undefined}>
{isDebug && (
<DebugPanel />
)}
</ConfigurableFormRenderer>
</ConditionalWrap>
</ParentProvider>
</ConditionalWrap>

</ConditionalWrap>
</div>
);
};
import ConditionalWrap from '@/components/conditionalWrapper';
import { DataContextProvider, MetadataProvider, useDataContextManager, useShaFormInstance } from '@/providers';
import { useFormDesigner, useFormDesignerFormMode, useFormDesignerIsDebug, useFormDesignerMarkup, useFormDesignerReadOnly, useFormDesignerSelectedComponent, useFormDesignerSettings } from '@/providers/formDesigner';
import ParentProvider from '@/providers/parentProvider';
import React, { FC, useMemo, useEffect, useCallback } from 'react';
import { ComponentPropertiesPanel } from '../componentPropertiesPanel';
import { ComponentPropertiesTitle } from '../componentPropertiesTitle';
import { DebugPanel } from '../debugPanel';
import { useStyles } from '../styles/styles';
import Toolbox from '../toolbox';
import { SheshaCommonContexts } from '@/providers/dataContextManager/models';
import { IViewType } from '@/providers/canvas/contexts';
import { SidebarContainer } from '@/components/sidebarContainer';
import { ConfigurableFormRenderer } from '@/components/configurableForm/configurableFormRenderer';

const rightSidebarProps = {
title: () => <ComponentPropertiesTitle />,
content: () => <ComponentPropertiesPanel />,
placeholder: 'Properties',
};

export const DesignerMainArea: FC<{ viewType?: IViewType }> = ({ viewType = 'configStudio' }) => {
const isDebug = useFormDesignerIsDebug();
const readOnly = useFormDesignerReadOnly();
const formSettings = useFormDesignerSettings();
const formMode = useFormDesignerFormMode();
const shaForm = useShaFormInstance();
const { antdForm: form } = shaForm;
const { styles } = useStyles();
const { deleteComponent, settingsPanelRef } = useFormDesigner();
const component = useFormDesignerSelectedComponent();

const showMarkup = false;
const markup = useFormDesignerMarkup();

const selectedComponentId = component?.id;
const noPageContext = !Boolean(useDataContextManager().getPageContext());

useEffect(() => {
if (shaForm) {
shaForm.applyMarkupAsync({
formFlatMarkup: shaForm.flatStructure,
formSettings: formSettings,
});
}
}, [formSettings, shaForm]);

const handleKeyDown = useCallback((event: KeyboardEvent) => {
if (readOnly || formMode !== 'designer' || event.repeat) return;

const isDelete = event.key === 'Delete';
const isBackspace = event.key === 'Backspace';

if (!isDelete && !isBackspace) return;

// Ignore if user is typing in an input, textarea, or contenteditable element
const target = event.target as HTMLElement;
const isEditing =
target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.isContentEditable;

if (isEditing) return;

// Ignore if focus is inside the properties/settings panel
if (settingsPanelRef?.current && settingsPanelRef.current.contains(target)) {
return;
}

if (selectedComponentId) {
event.preventDefault();
deleteComponent({ componentId: selectedComponentId });
}
}, [readOnly, formMode, selectedComponentId, deleteComponent, settingsPanelRef]);

useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown]);

const leftSidebarProps = useMemo(() =>
readOnly ? null : { title: 'Builder Components', content: () => <Toolbox />, placeholder: 'Builder Components' },
[readOnly]);

return (
<div
className={styles.mainArea}
style={{
borderTop: '1px solid #d3d3d3',
...(formMode !== 'designer' && {
maxHeight: '85vh',
overflow: 'auto',
}),
}}
>
<ConditionalWrap
condition={formMode === 'designer'}
wrap={(children) => (
<SidebarContainer
leftSidebarProps={leftSidebarProps}
rightSidebarProps={rightSidebarProps}
canZoom={true}
viewType={viewType}
>
{children}
</SidebarContainer>
)}
>
<ConditionalWrap
condition={Boolean(formSettings?.modelType)}
wrap={(children) => (<MetadataProvider modelType={formSettings?.modelType}>{children}</MetadataProvider>)}
>
{/* Use special format of parent properties to avoid adding form context */}
<ParentProvider model={null} formMode="designer" name="designer" isScope addContext={false}>
{/* pageContext has added only to customize the designed form. It is not used as a data context.*/}
{/* formContext has added only to customize the designed form. It is not used as a data context.*/}
<ConditionalWrap
condition={noPageContext}
wrap={(children) => (
<DataContextProvider
id="pageContext"
description="Designer Page context"
name={SheshaCommonContexts.PageContext}
type="page"
webStorageType="sessionStorage"
>
<DataContextProvider
id="formContext"
description="Designer Form context"
name={SheshaCommonContexts.FormContext}
type="form"
webStorageType="sessionStorage"
>
{children}
</DataContextProvider>
</DataContextProvider>
)}
>

{showMarkup && <textarea readOnly value={JSON.stringify(markup, null, 2)} /> /* ToDo: AS - remove after inheritance implementation */}
<ConfigurableFormRenderer form={form} className={formMode === 'designer' ? styles.designerWorkArea : undefined}>
{isDebug && (
<DebugPanel />
)}
</ConfigurableFormRenderer>
</ConditionalWrap>
</ParentProvider>
</ConditionalWrap>

</ConditionalWrap>
</div>
);
};
33 changes: 1 addition & 32 deletions shesha-reactjs/src/components/formDesigner/styles/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const designerClassNames = {
mainArea: "sha-designer-main-area",
previewBorderTop10: "preview-form-border-top-10",
shaComponent: "sha-component",
shaComponentControls: "sha-component-controls",
shaComponentGhost: "sha-component-ghost",
shaComponentIndicator: "sha-component-indicator",
shaComponentSearch: "sha-component-search",
Expand Down Expand Up @@ -85,7 +84,6 @@ export const useMainStyles = createStyles(({ css, cx, token, prefixCls, iconPref
shaDropHint,
designerWorkArea,
componentPropertiesActions,
shaComponentControls,
siteTreeSearchValue,
shaDesignerWarning,
hasConfigErrors,
Expand Down Expand Up @@ -423,7 +421,7 @@ export const useMainStyles = createStyles(({ css, cx, token, prefixCls, iconPref
}
.${shaComponent} {
position: relative;
min-height: 50px; /* Ensure enough space for delete button and error icon */
min-height: 50px; /* Ensure enough space for error icon */

.${prefixCls}-alert.${shaDesignerWarning} {
margin-bottom: 0;
Expand All @@ -444,17 +442,6 @@ export const useMainStyles = createStyles(({ css, cx, token, prefixCls, iconPref
}
}

// Error icon positioning: snap to corner when not hovering
.sha-error-icon-top-right {
top: 4px !important;
transition: top 0.2s ease;
}

// When hovering the component, move error icon down to make room for delete button
&:hover .sha-error-icon-top-right {
top: 28px !important;
}

.${shaComponentValidationIcon} {
display: flex;
align-items: center;
Expand Down Expand Up @@ -489,10 +476,6 @@ export const useMainStyles = createStyles(({ css, cx, token, prefixCls, iconPref
padding: 5px 3px;
}
&:not(:hover) {
.${shaComponentControls} {
display: none;
}

.${componentDragHandle} {
border: 1px solid white;
}
Expand All @@ -512,21 +495,7 @@ export const useMainStyles = createStyles(({ css, cx, token, prefixCls, iconPref
}
}

.${shaComponentControls} {
text-align: right;
position: absolute;
right: 5px;
top: 5px;
display: block;
min-height: 20px;
z-index: 1000;
}

&:not(:hover) {
.${shaComponentControls} {
display: none;
}

.${componentDragHandle} {
background-color: transparent;
}
Expand Down
Loading
Loading