diff --git a/packages/arco-lib/src/components/Table/Table.tsx b/packages/arco-lib/src/components/Table/Table.tsx index 041235e7..ac776eb8 100644 --- a/packages/arco-lib/src/components/Table/Table.tsx +++ b/packages/arco-lib/src/components/Table/Table.tsx @@ -386,6 +386,7 @@ export const Table = implementRuntimeComponent({ componentId: handler.componentId, name: handler.method.name, parameters: handler.method.parameters || {}, + triggerId: component.id, }); }); }; diff --git a/packages/chakra-ui-lib/src/components/Form/Form.tsx b/packages/chakra-ui-lib/src/components/Form/Form.tsx index 2af76425..41fab413 100644 --- a/packages/chakra-ui-lib/src/components/Form/Form.tsx +++ b/packages/chakra-ui-lib/src/components/Form/Form.tsx @@ -88,11 +88,13 @@ export default implementRuntimeComponent({ services.apiService.send('uiMethod', { componentId: inputId, name: 'resetInputValue', + triggerId: component.id, }); }); }, }); }, [ + component.id, formControlIds, services.apiService, services.stateManager.store, diff --git a/packages/chakra-ui-lib/src/components/Table/TableTd.tsx b/packages/chakra-ui-lib/src/components/Table/TableTd.tsx index fddc4817..ab7d521d 100644 --- a/packages/chakra-ui-lib/src/components/Table/TableTd.tsx +++ b/packages/chakra-ui-lib/src/components/Table/TableTd.tsx @@ -90,6 +90,7 @@ export const TableTd: React.FC<{ componentId: evaledHandler.componentId, name: evaledHandler.method.name, parameters: evaledHandler.method.parameters, + triggerId: component.id, }); }); }; diff --git a/packages/editor-sdk/src/components/ApiForm/ApiForm.tsx b/packages/editor-sdk/src/components/ApiForm/ApiForm.tsx index 92d96a7c..7a74063d 100644 --- a/packages/editor-sdk/src/components/ApiForm/ApiForm.tsx +++ b/packages/editor-sdk/src/components/ApiForm/ApiForm.tsx @@ -74,6 +74,7 @@ export const ApiForm: React.FC = props => { componentId: component.id, name: 'triggerFetch', parameters: {}, + triggerId: component.id, }); }, [services.apiService, component]); const onMethodChange = useCallback( diff --git a/packages/editor/src/components/WarningArea.tsx b/packages/editor/src/components/WarningArea.tsx deleted file mode 100644 index d9c66774..00000000 --- a/packages/editor/src/components/WarningArea.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; -import { - Badge, - Button, - HStack, - IconButton, - Table, - Tbody, - Td, - Text, - Th, - Thead, - Tr, - VStack, -} from '@chakra-ui/react'; -import { observer } from 'mobx-react-lite'; -import React, { useMemo } from 'react'; -import { EditorServices } from '../types'; -import { Pagination } from './Pagination'; - -type Props = { - services: EditorServices; -}; - -export const WarningArea: React.FC = observer(({ services }) => { - const { editorStore } = services; - const [isCollapsed, setIsCollapsed] = React.useState(true); - const [currPage, setCurrPage] = React.useState(0); - const PageSize = 5; - const { validateResult, setSelectedComponentId } = editorStore; - const errorItems = useMemo(() => { - if (isCollapsed) { - return null; - } - return validateResult - .slice(currPage * PageSize, currPage * PageSize + PageSize) - .map((result, i) => { - return ( - - setSelectedComponentId(result.componentId)} - > - {result.componentId} - - {result.traitType || '-'} - {result.property || '-'} - {result.message} - - ); - }); - }, [currPage, isCollapsed, setSelectedComponentId, validateResult]); - - const savedBadge = useMemo(() => { - return Saved; - }, []); - - const unsaveBadge = useMemo(() => { - return ( - - - Unsave - - ); - }, [editorStore]); - - return ( - - - - Errors - - {editorStore.validateResult.length} - - - - {editorStore.isSaved ? savedBadge : unsaveBadge} - : } - onClick={() => setIsCollapsed(prev => !prev)} - /> - - - - - - - - - - - - - {errorItems} -
Component IdTrait TypePropertyMessage
- setCurrPage(page)} - /> -
-
- ); -}); diff --git a/packages/editor/src/components/WarningArea/ErrorLogs.tsx b/packages/editor/src/components/WarningArea/ErrorLogs.tsx new file mode 100644 index 00000000..ec084a2d --- /dev/null +++ b/packages/editor/src/components/WarningArea/ErrorLogs.tsx @@ -0,0 +1,46 @@ +import { Props } from './type'; +import React from 'react'; +import { DebugTable } from './Table'; +import { Box } from '@chakra-ui/react'; + +export const ErrorLogs: React.FC = ({ services }) => { + const { validateResult, setSelectedComponentId } = services.editorStore; + const errorColumns = [ + { + title: 'Component Id', + dataIndex: 'componentId', + render: (_col: any, item: any) => { + return ( + setSelectedComponentId(item.componentId)} + > + {item.componentId} + + ); + }, + }, + { + title: 'Trait Type', + dataIndex: 'traitType', + }, + { + title: 'Property', + dataIndex: 'property', + }, + { + title: 'Message', + dataIndex: 'message', + }, + ]; + + return ( + + ); +}; diff --git a/packages/editor/src/components/WarningArea/EventLogs.tsx b/packages/editor/src/components/WarningArea/EventLogs.tsx new file mode 100644 index 00000000..22911b23 --- /dev/null +++ b/packages/editor/src/components/WarningArea/EventLogs.tsx @@ -0,0 +1,99 @@ +import { Props, EventLog } from './type'; +import React from 'react'; +import { DebugTable } from './Table'; +import { Box, Button, Tooltip } from '@chakra-ui/react'; +import { css } from '@emotion/css'; + +type EventLogsProps = Props & { + events: EventLog[]; + setEventLogs: React.Dispatch>; +}; + +export const EventLogs: React.FC = ({ + services, + events, + setEventLogs, +}) => { + const { setSelectedComponentId } = services.editorStore; + + const eventColumns = [ + { + title: 'Time', + dataIndex: 'time', + }, + { + title: 'Event Type', + dataIndex: 'type', + }, + { + title: 'Target', + dataIndex: 'target', + render: (_col: any, item: any) => { + return ( + setSelectedComponentId(item.target)} + > + {item.target} + + ); + }, + }, + { + title: 'Method', + dataIndex: 'methodName', + }, + { + title: 'Parameters', + dataIndex: 'parameters', + render: (_col: any, item: any) => { + const parameters = JSON.stringify(item.parameters || ''); + return ( + + {parameters.length > 16 ? '...' : parameters} + + ); + }, + }, + { + title: 'TriggerId', + dataIndex: 'triggerId', + render: (_col: any, item: any) => { + return ( + setSelectedComponentId(item.triggerId)} + > + {item.triggerId} + + ); + }, + }, + ]; + + return ( + { + setEventLogs([]); + }} + size="sm" + > + clear + + ) + } + /> + ); +}; diff --git a/packages/editor/src/components/Pagination.tsx b/packages/editor/src/components/WarningArea/Pagination.tsx similarity index 92% rename from packages/editor/src/components/Pagination.tsx rename to packages/editor/src/components/WarningArea/Pagination.tsx index 2f92e10f..7a07ea6e 100644 --- a/packages/editor/src/components/Pagination.tsx +++ b/packages/editor/src/components/WarningArea/Pagination.tsx @@ -5,6 +5,7 @@ import React from 'react'; type Props = { currentPage: number; lastPage: number; + hideOnSinglePage?: boolean; handlePageClick: (page: number) => void; }; @@ -12,7 +13,12 @@ export const Pagination: React.FC = ({ currentPage, lastPage, handlePageClick, + hideOnSinglePage = false, }) => { + if (lastPage === 1 && hideOnSinglePage) { + return null; + } + const pages = []; if (currentPage - 1 >= 0) { const prevPage = currentPage - 1; @@ -37,7 +43,7 @@ export const Pagination: React.FC = ({ startIdx = 0; endIdx = ShowPagesNumber; } - if (currentPage + 3 >= lastPage) { + if (currentPage + 4 >= lastPage) { startIdx = Math.max(0, lastPage - ShowPagesNumber); endIdx = lastPage; } diff --git a/packages/editor/src/components/WarningArea/Table.tsx b/packages/editor/src/components/WarningArea/Table.tsx new file mode 100644 index 00000000..82ad594d --- /dev/null +++ b/packages/editor/src/components/WarningArea/Table.tsx @@ -0,0 +1,93 @@ +import React, { ReactNode, useEffect } from 'react'; +import { Box, HStack, Table, Tbody, Td, Th, Thead, Tr, VStack } from '@chakra-ui/react'; +import { Pagination } from './Pagination'; +import { defaultPageSize } from './const'; + +type Column = { + title: string; + dataIndex: string; + render?: (col: any, item: any, index: number) => React.ReactElement; +}; + +type TableProps = { + className?: string; + columns: Column[]; + pagination?: { + pageSize?: number; + lastPage?: number; + hideOnSinglePage?: boolean; + }; + data: any[]; + footer?: ReactNode; + emptyMessage?: string; +}; + +export const DebugTable: React.FC = ({ + columns = [], + pagination = {}, + data, + footer, + emptyMessage = 'No Data', + className, +}) => { + const { pageSize = defaultPageSize, hideOnSinglePage = false, lastPage } = pagination; + const [currPage, setCurrPage] = React.useState(0); + const currentData = data.slice(currPage * pageSize, currPage * pageSize + pageSize); + + useEffect(() => { + if (data.length <= Number(pageSize) * (currPage - 1)) { + setCurrPage(0); + } + }, [currPage, data.length, pageSize]); + + return ( + + + + + {columns.map(c => { + return ; + })} + + + + {!data.length ? ( + + + + ) : ( + currentData.map((d, i) => { + return ( + + {columns.map(c => { + if (c.render) { + return ; + } + return ; + })} + + ); + }) + )} + +
{c.title}
+ {emptyMessage} +
{c.render(c, d, i)}{d[c.dataIndex]}
+ + {footer} + setCurrPage(page)} + hideOnSinglePage={hideOnSinglePage} + /> + +
+ ); +}; diff --git a/packages/editor/src/components/WarningArea/WarningArea.tsx b/packages/editor/src/components/WarningArea/WarningArea.tsx new file mode 100644 index 00000000..fe511ec7 --- /dev/null +++ b/packages/editor/src/components/WarningArea/WarningArea.tsx @@ -0,0 +1,125 @@ +import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; +import { + Badge, + Button, + HStack, + IconButton, + Tabs, + Text, + TabPanel, + TabPanels, + TabList, + VStack, + Tab, +} from '@chakra-ui/react'; +import { observer } from 'mobx-react-lite'; +import React, { useEffect, useMemo, useState } from 'react'; +import { EventLogs } from './EventLogs'; +import { ErrorLogs } from './ErrorLogs'; +import type { Props, Event, EventLog } from './type'; +import produce from 'immer'; + +export const WarningArea: React.FC = observer(({ services }) => { + const { editorStore } = services; + const [isCollapsed, setIsCollapsed] = React.useState(true); + const [eventLogs, setEventLogs] = useState([]); + + useEffect(() => { + const handler = (type: string, event: unknown) => { + setEventLogs(cur => { + return produce(cur, draft => { + const { name, triggerId, componentId, parameters } = event as Event; + draft.unshift({ + type, + methodName: name, + triggerId, + time: new Date().toLocaleTimeString(), + target: componentId, + parameters, + }); + }); + }); + }; + services.apiService.on('*', handler); + return () => services.apiService.off('*', handler); + }, [services.apiService]); + + const savedBadge = useMemo(() => { + return Saved; + }, []); + + const unsaveBadge = useMemo(() => { + return ( + + + Unsave + + ); + }, [editorStore]); + + return ( + + + + + + + Errors + + + {editorStore.validateResult.length} + + + Logs + + {editorStore.isSaved ? savedBadge : unsaveBadge} + : } + onClick={() => setIsCollapsed(prev => !prev)} + /> + + + {!isCollapsed && ( + + + + + + + + + )} + + + + ); +}); diff --git a/packages/editor/src/components/WarningArea/const.ts b/packages/editor/src/components/WarningArea/const.ts new file mode 100644 index 00000000..d43298c3 --- /dev/null +++ b/packages/editor/src/components/WarningArea/const.ts @@ -0,0 +1 @@ +export const defaultPageSize = 5; diff --git a/packages/editor/src/components/WarningArea/index.ts b/packages/editor/src/components/WarningArea/index.ts new file mode 100644 index 00000000..570f7d4f --- /dev/null +++ b/packages/editor/src/components/WarningArea/index.ts @@ -0,0 +1 @@ +export * from './WarningArea'; diff --git a/packages/editor/src/components/WarningArea/type.ts b/packages/editor/src/components/WarningArea/type.ts new file mode 100644 index 00000000..249ea549 --- /dev/null +++ b/packages/editor/src/components/WarningArea/type.ts @@ -0,0 +1,20 @@ +import { EditorServices } from '../../types'; + +export type Props = { + services: EditorServices; +}; + +export type Event = { + componentId: string; + name: string; + parameters: any; + triggerId: string; +}; +export type EventLog = { + time: string; + type: string; + target: string; + methodName: string; + triggerId: string; + parameters: any; +}; diff --git a/packages/runtime/src/components/_internal/ModuleRenderer.tsx b/packages/runtime/src/components/_internal/ModuleRenderer.tsx index c078dd63..2e0434c4 100644 --- a/packages/runtime/src/components/_internal/ModuleRenderer.tsx +++ b/packages/runtime/src/components/_internal/ModuleRenderer.tsx @@ -134,6 +134,7 @@ const ModuleRendererContent = React.forwardRef< componentId: evaledHandler.componentId, name: evaledHandler.method.name, parameters: evaledHandler.method.parameters, + triggerId: moduleId, }); } }; diff --git a/packages/runtime/src/services/apiService.ts b/packages/runtime/src/services/apiService.ts index fc2931ac..a1dadb0e 100644 --- a/packages/runtime/src/services/apiService.ts +++ b/packages/runtime/src/services/apiService.ts @@ -10,6 +10,7 @@ export function initApiService() { uiMethod: { componentId: string; name: string; + triggerId: string; parameters?: any; }; moduleEvent: { diff --git a/packages/runtime/src/traits/core/Event.tsx b/packages/runtime/src/traits/core/Event.tsx index 3c744942..21219ad2 100644 --- a/packages/runtime/src/traits/core/Event.tsx +++ b/packages/runtime/src/traits/core/Event.tsx @@ -26,7 +26,7 @@ export default implementRuntimeTrait({ state: {}, }, })(() => { - return ({ trait, handlers, services, slotKey }) => { + return ({ trait, handlers, services, slotKey, componentId }) => { const callbackQueueMap: Record void>> = {}; const rawHandlers = trait.properties.handlers; // setup current handlers @@ -37,7 +37,7 @@ export default implementRuntimeTrait({ callbackQueueMap[handler.type] = []; } callbackQueueMap[handler.type].push( - runEventHandler(handler, rawHandlers, Number(i), services, slotKey) + runEventHandler(handler, rawHandlers, Number(i), services, slotKey, componentId) ); } @@ -60,7 +60,7 @@ export default implementRuntimeTrait({ () => { handlers.forEach((h, i) => { if (h.type === MountEvent.mount) { - runEventHandler(h, rawHandlers, i, services, slotKey)(); + runEventHandler(h, rawHandlers, i, services, slotKey, componentId)(); } }); }, @@ -69,7 +69,7 @@ export default implementRuntimeTrait({ () => { handlers.forEach((h, i) => { if (h.type === MountEvent.update) { - runEventHandler(h, rawHandlers, i, services, slotKey)(); + runEventHandler(h, rawHandlers, i, services, slotKey, componentId)(); } }); }, @@ -78,7 +78,7 @@ export default implementRuntimeTrait({ () => { handlers.forEach((h, i) => { if (h.type === MountEvent.unmount) { - runEventHandler(h, rawHandlers, i, services, slotKey)(); + runEventHandler(h, rawHandlers, i, services, slotKey, componentId)(); } }); }, diff --git a/packages/runtime/src/traits/core/Fetch.tsx b/packages/runtime/src/traits/core/Fetch.tsx index ae533148..2cd1ac87 100644 --- a/packages/runtime/src/traits/core/Fetch.tsx +++ b/packages/runtime/src/traits/core/Fetch.tsx @@ -171,7 +171,8 @@ export default implementRuntimeTrait({ rawOnComplete, index, services, - slotKey + slotKey, + componentId )(); }); } else { @@ -189,7 +190,14 @@ export default implementRuntimeTrait({ const rawOnError = trait.properties.onError; onError?.forEach((_, index) => { - runEventHandler(onError[index], rawOnError, index, services, slotKey)(); + runEventHandler( + onError[index], + rawOnError, + index, + services, + slotKey, + componentId + )(); }); } }, @@ -208,7 +216,14 @@ export default implementRuntimeTrait({ const rawOnError = trait.properties.onError; onError?.forEach((_, index) => { - runEventHandler(onError[index], rawOnError, index, services, slotKey)(); + runEventHandler( + onError[index], + rawOnError, + index, + services, + slotKey, + componentId + )(); }); } ); diff --git a/packages/runtime/src/utils/runEventHandler.ts b/packages/runtime/src/utils/runEventHandler.ts index 702bfa1f..00becd0e 100644 --- a/packages/runtime/src/utils/runEventHandler.ts +++ b/packages/runtime/src/utils/runEventHandler.ts @@ -11,7 +11,8 @@ export const runEventHandler = ( rawHandlers: string | PropsBeforeEvaled>, index: number, services: UIServices, - slotKey: string + slotKey: string, + triggerId = '' ) => { const { stateManager } = services; const send = () => { @@ -36,6 +37,7 @@ export const runEventHandler = ( componentId: evaledHandler.componentId, name: evaledHandler.method.name, parameters: evaledHandler.method.parameters, + triggerId, }); }; const { wait } = handler;