Skip to content

Actions JS Console #1853

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Adds dynamic layer indexing
  • Loading branch information
kamalqureshi authored and raheeliftikhar5 committed Jul 22, 2025
commit d3142c1c35500f5974e994763f415c34ffd474e4
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
changeLayoutAction,
addEventHandlerAction,
applyStyleAction,
nestComponentAction
nestComponentAction,
updateDynamicLayoutAction
} from "./actions";

export const actionCategories: ActionCategory[] = [
Expand All @@ -33,7 +34,7 @@ export const actionCategories: ActionCategory[] = [
{
key: 'layout',
label: 'Layout',
actions: [changeLayoutAction]
actions: [changeLayoutAction, updateDynamicLayoutAction]
},
{
key: 'events',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import { default as Space } from "antd/es/space";
import { default as Flex } from "antd/es/flex";
import type { InputRef } from 'antd';
import { default as DownOutlined } from "@ant-design/icons/DownOutlined";
import { BaseSection } from "lowcoder-design";
import { BaseSection, Dropdown } from "lowcoder-design";
import { EditorContext } from "comps/editorState";
import { message } from "antd";
import { CustomDropdown } from "./styled";
import { generateComponentActionItems, getComponentCategories } from "./utils";
import { generateComponentActionItems, getComponentCategories, getEditorComponentInfo, getLayoutItemsOrder } from "./utils";
import { actionRegistry, getAllActionItems } from "./actionConfigs";

export function ActionInputSection() {
Expand All @@ -31,6 +31,8 @@ export function ActionInputSection() {
const [showStylingInput, setShowStylingInput] = useState<boolean>(false);
const [selectedEditorComponent, setSelectedEditorComponent] = useState<string | null>(null);
const [validationError, setValidationError] = useState<string | null>(null);
const [showDynamicLayoutDropdown, setShowDynamicLayoutDropdown] = useState<boolean>(false);
const [selectedDynamicLayoutIndex, setSelectedDynamicLayoutIndex] = useState<string | null>(null);
const inputRef = useRef<InputRef>(null);
const editorState = useContext(EditorContext);

Expand All @@ -56,6 +58,25 @@ export function ActionInputSection() {
}));
}, [editorState]);

const simpleLayoutItems = useMemo(() => {
if(!editorComponents) return [];

const editorComponentInfo = getEditorComponentInfo(editorState);
if(!editorComponentInfo) return [];

const currentLayout = editorComponentInfo.currentLayout;
const items = editorComponentInfo.items;

return Object.keys(currentLayout).map((key) => {
const item = items ? items[key] : null;
const componentName = item ? (item as any).children.name.getView() : key;
return {
label: componentName,
key: componentName
};
});
}, [editorState]);

const currentAction = useMemo(() => {
return selectedActionKey ? actionRegistry.get(selectedActionKey) : null;
}, [selectedActionKey]);
Expand All @@ -81,7 +102,9 @@ export function ActionInputSection() {
setSelectedEditorComponent(null);
setIsNestedComponent(false);
setSelectedNestComponent(null);
setShowDynamicLayoutDropdown(false);
setActionValue("");
setSelectedDynamicLayoutIndex(null);

if (action.requiresComponentSelection) {
setShowComponentDropdown(true);
Expand All @@ -103,6 +126,9 @@ export function ActionInputSection() {
if (action.isNested) {
setIsNestedComponent(true);
}
if(action.dynamicLayout) {
setShowDynamicLayoutDropdown(true);
}
}, []);

const handleComponentSelection = useCallback((key: string) => {
Expand Down Expand Up @@ -175,6 +201,7 @@ export function ActionInputSection() {
selectedComponent,
selectedEditorComponent,
selectedNestComponent,
selectedDynamicLayoutIndex,
editorState
});

Expand All @@ -189,6 +216,8 @@ export function ActionInputSection() {
setValidationError(null);
setIsNestedComponent(false);
setSelectedNestComponent(null);
setShowDynamicLayoutDropdown(false);
setSelectedDynamicLayoutIndex(null);

} catch (error) {
console.error('Error executing action:', error);
Expand All @@ -200,6 +229,7 @@ export function ActionInputSection() {
selectedComponent,
selectedEditorComponent,
selectedNestComponent,
selectedDynamicLayoutIndex,
editorState,
currentAction,
validateInput
Expand Down Expand Up @@ -299,7 +329,7 @@ export function ActionInputSection() {
popupRender={() => (
<Menu
items={editorComponents}
onClick={({ key }) => {
onClick={({key}) => {
handleEditorComponentSelection(key);
}}
/>
Expand All @@ -314,6 +344,47 @@ export function ActionInputSection() {
</CustomDropdown>
)}

{showDynamicLayoutDropdown && (
<CustomDropdown
overlayStyle={{
maxHeight: '400px',
overflow: 'auto',
zIndex: 9999
}}
popupRender={() => (
<Menu
items={simpleLayoutItems}
onClick={({key}) => {
handleEditorComponentSelection(key);
}}
/>
)}
>
<Button size={"small"}>
<Space>
{selectedEditorComponent ? selectedEditorComponent : 'Layout'}
<DownOutlined />
</Space>
</Button>
</CustomDropdown>
)}

{showDynamicLayoutDropdown && (
<Dropdown
options={getLayoutItemsOrder(simpleLayoutItems)}
onChange={(value) => {
setSelectedDynamicLayoutIndex(value);
}}
>
<Button size={"small"}>
<Space>
{selectedEditorComponent ? selectedEditorComponent : 'Layout'}
<DownOutlined />
</Space>
</Button>
</Dropdown>
)}

{shouldShowInput && (
showStylingInput ? (
<Input.TextArea
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { message } from "antd";
import { ActionConfig, ActionExecuteParams } from "../types";
import { getEditorComponentInfo } from "../utils";
import { changeValueAction, multiChangeAction, wrapActionExtraInfo } from "lowcoder-core";

export const changeLayoutAction: ActionConfig = {
key: 'change-layout',
Expand All @@ -16,4 +18,101 @@ export const changeLayoutAction: ActionConfig = {

// TODO: Implement actual layout change logic
}
};

export const updateDynamicLayoutAction: ActionConfig = {
key: 'update-dynamic-layout',
label: 'Update Dynamic Layout',
category: 'layout',
requiresInput: false,
dynamicLayout: true,
execute: async (params: ActionExecuteParams) => {
const { selectedDynamicLayoutIndex, selectedEditorComponent, editorState } = params;

if (!selectedEditorComponent || !editorState || !selectedDynamicLayoutIndex) {
message.error('Component, editor state, and layout index are required');
return;
}

try {
const componentInfo = getEditorComponentInfo(editorState, selectedEditorComponent);

if (!componentInfo) {
message.error(`Component "${selectedEditorComponent}" not found`);
return;
}

const { componentKey, currentLayout, simpleContainer, items } = componentInfo;

if (!componentKey || !currentLayout[componentKey]) {
message.error(`Component "${selectedEditorComponent}" not found in layout`);
return;
}

const currentLayoutItem = currentLayout[componentKey];
const newPos = parseInt(selectedDynamicLayoutIndex);

if (isNaN(newPos)) {
message.error('Invalid layout index provided');
return;
}

const currentPos = currentLayoutItem.pos || 0;
const layoutItems = Object.entries(currentLayout).map(([key, item]) => ({
key,
item: item as any,
pos: (item as any).pos || 0
})).sort((a, b) => a.pos - b.pos);

const otherItems = layoutItems.filter(item => item.key !== componentKey);
const newLayout: any = {};

newLayout[componentKey] = {
...currentLayoutItem,
pos: newPos
};

// Update other components with shifted positions
otherItems.forEach((item) => {
let adjustedPos = item.pos;

// If moving to a position before the current position, shift items in between
if (newPos < currentPos && item.pos >= newPos && item.pos < currentPos) {
adjustedPos = item.pos + 1;
}
// If moving to a position after the current position, shift items in between
else if (newPos > currentPos && item.pos > currentPos && item.pos <= newPos) {
adjustedPos = item.pos - 1;
}

newLayout[item.key] = {
...item.item,
pos: adjustedPos
};
});

simpleContainer.dispatch(
wrapActionExtraInfo(
multiChangeAction({
layout: changeValueAction(newLayout, true),
}),
{
compInfos: [{
compName: selectedEditorComponent,
compType: (items[componentKey] as any).children.compType.getView(),
type: "layout"
}]
}
)
);

editorState.setSelectedCompNames(new Set([selectedEditorComponent]), "layoutComp");

message.success(`Component "${selectedEditorComponent}" moved to position ${newPos}`);

} catch (error) {
console.error('Error updating dynamic layout:', error);
message.error('Failed to update dynamic layout. Please try again.');
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export * from './componentManagement';
export { configureComponentAction } from './componentConfiguration';

// Layout Actions
export { changeLayoutAction } from './componentLayout';
export { changeLayoutAction, updateDynamicLayoutAction } from './componentLayout';

// Event Actions
export { addEventHandlerAction } from './componentEvents';
Expand Down
2 changes: 2 additions & 0 deletions client/packages/lowcoder/src/comps/comps/preLoadComp/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface ActionConfig {
requiresInput?: boolean;
requiresStyle?: boolean;
isNested?: boolean;
dynamicLayout?: boolean;
inputPlaceholder?: string;
inputType?: 'text' | 'number' | 'textarea' | 'json';
validation?: (value: string) => string | null;
Expand All @@ -48,6 +49,7 @@ export interface ActionExecuteParams {
selectedComponent: string | null;
selectedEditorComponent: string | null;
selectedNestComponent: string | null;
selectedDynamicLayoutIndex: string | null;
editorState: any;
}

Expand Down
25 changes: 22 additions & 3 deletions client/packages/lowcoder/src/comps/comps/preLoadComp/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function getComponentCategories() {
});
return cats;
}
export function getEditorComponentInfo(editorState: EditorState, componentName: string): {
export function getEditorComponentInfo(editorState: EditorState, componentName?: string): {
componentKey: string | null;
currentLayout: any;
simpleContainer: any;
Expand All @@ -63,7 +63,7 @@ export function getEditorComponentInfo(editorState: EditorState, componentName:
} | null {
try {
// Get the UI component container
if (!editorState || !componentName) {
if (!editorState) {
return null;
}

Expand All @@ -85,6 +85,16 @@ export function getEditorComponentInfo(editorState: EditorState, componentName:
const currentLayout = simpleContainer.children.layout.getView();
const items = getCombinedItems(uiCompTree);

// If no componentName is provided, return all items
if (!componentName) {
return {
componentKey: null,
currentLayout,
simpleContainer,
items,
};
}

// Find the component by name and get its key
let componentKey: string | null = null;
let componentType: string | null = null;
Expand All @@ -93,7 +103,7 @@ export function getEditorComponentInfo(editorState: EditorState, componentName:
if ((item as any).children.name.getView() === componentName) {
componentKey = key;
componentType = (item as any).children.compType.getView();
break
break;
}
}

Expand Down Expand Up @@ -137,3 +147,12 @@ function getCombinedItems(uiCompTree: any) {

return combined;
}

export function getLayoutItemsOrder(layoutItems: any[]){
const maxIndex = layoutItems.length;
return Array.from({ length: maxIndex }, (_, index) => ({
key: index,
label: `Position ${index}`,
value: index.toString()
}));
}