Extract the JSON visualizer component in twenty-ui (#10937)
- Move the JsonTree component and the other components to twenty-ui - Rely on a React Context to provide translations ## Future work It would be good to migrate the `createRequiredContext` function to `twenty-ui`. I didn't want to migrate it in this PR but would have liked to use it.
This commit is contained in:
committed by
GitHub
parent
428499e222
commit
093d6c0a1a
@ -1,8 +1,8 @@
|
|||||||
import { SettingsAdminIndicatorHealthContext } from '@/settings/admin-panel/health-status/contexts/SettingsAdminIndicatorHealthContext';
|
import { SettingsAdminIndicatorHealthContext } from '@/settings/admin-panel/health-status/contexts/SettingsAdminIndicatorHealthContext';
|
||||||
import { JsonTree } from '@/workflow/components/json-visualizer/components/JsonTree';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { Section } from 'twenty-ui';
|
import { JsonTree, Section } from 'twenty-ui';
|
||||||
import { AdminPanelHealthServiceStatus } from '~/generated/graphql';
|
import { AdminPanelHealthServiceStatus } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledDetailsContainer = styled.div`
|
const StyledDetailsContainer = styled.div`
|
||||||
@ -21,6 +21,8 @@ const StyledErrorMessage = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const JsonDataIndicatorHealthStatus = () => {
|
export const JsonDataIndicatorHealthStatus = () => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { indicatorHealth } = useContext(SettingsAdminIndicatorHealthContext);
|
const { indicatorHealth } = useContext(SettingsAdminIndicatorHealthContext);
|
||||||
|
|
||||||
const parsedDetails = indicatorHealth.details
|
const parsedDetails = indicatorHealth.details
|
||||||
@ -41,7 +43,13 @@ export const JsonDataIndicatorHealthStatus = () => {
|
|||||||
)}
|
)}
|
||||||
{parsedDetails && (
|
{parsedDetails && (
|
||||||
<StyledDetailsContainer>
|
<StyledDetailsContainer>
|
||||||
<JsonTree value={parsedDetails} />
|
<JsonTree
|
||||||
|
value={parsedDetails}
|
||||||
|
emptyArrayLabel={t`Empty Array`}
|
||||||
|
emptyObjectLabel={t`Empty Object`}
|
||||||
|
arrowButtonCollapsedLabel={t`Expand`}
|
||||||
|
arrowButtonExpandedLabel={t`Collapse`}
|
||||||
|
/>
|
||||||
</StyledDetailsContainer>
|
</StyledDetailsContainer>
|
||||||
)}
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
import { JsonList } from '@/workflow/components/json-visualizer/components/internal/JsonList';
|
|
||||||
import { JsonNode } from '@/workflow/components/json-visualizer/components/JsonNode';
|
|
||||||
import { JsonValue } from 'type-fest';
|
|
||||||
|
|
||||||
export const JsonTree = ({
|
|
||||||
value,
|
|
||||||
shouldHighlightNode,
|
|
||||||
}: {
|
|
||||||
value: JsonValue;
|
|
||||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<JsonList depth={0}>
|
|
||||||
<JsonNode
|
|
||||||
value={value}
|
|
||||||
depth={0}
|
|
||||||
keyPath=""
|
|
||||||
shouldHighlightNode={shouldHighlightNode}
|
|
||||||
/>
|
|
||||||
</JsonList>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,11 +1,15 @@
|
|||||||
import { JsonNestedNode } from '@/workflow/components/json-visualizer/components/JsonNestedNode';
|
|
||||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||||
import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
|
import { getWorkflowRunStepContext } from '@/workflow/workflow-steps/utils/getWorkflowRunStepContext';
|
||||||
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
|
import { getWorkflowVariablesUsedInStep } from '@/workflow/workflow-steps/utils/getWorkflowVariablesUsedInStep';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { IconBrackets } from 'twenty-ui';
|
import {
|
||||||
|
IconBrackets,
|
||||||
|
JsonNestedNode,
|
||||||
|
JsonTreeContextProvider,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -15,6 +19,8 @@ const StyledContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
const workflowRunId = useWorkflowRunIdOrThrow();
|
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||||
const step = workflowRun?.output?.flow.steps.find(
|
const step = workflowRun?.output?.flow.steps.find(
|
||||||
@ -48,18 +54,27 @@ export const WorkflowRunStepInputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<JsonNestedNode
|
<JsonTreeContextProvider
|
||||||
elements={stepContext.map(({ id, name, context }) => ({
|
value={{
|
||||||
id,
|
emptyArrayLabel: t`Empty Array`,
|
||||||
label: name,
|
emptyObjectLabel: t`Empty Object`,
|
||||||
value: context,
|
arrowButtonCollapsedLabel: t`Expand`,
|
||||||
}))}
|
arrowButtonExpandedLabel: t`Collapse`,
|
||||||
Icon={IconBrackets}
|
shouldHighlightNode: (keyPath) => variablesUsedInStep.has(keyPath),
|
||||||
emptyElementsText=""
|
}}
|
||||||
depth={0}
|
>
|
||||||
keyPath=""
|
<JsonNestedNode
|
||||||
shouldHighlightNode={(keyPath) => variablesUsedInStep.has(keyPath)}
|
elements={stepContext.map(({ id, name, context }) => ({
|
||||||
/>
|
id,
|
||||||
|
label: name,
|
||||||
|
value: context,
|
||||||
|
}))}
|
||||||
|
Icon={IconBrackets}
|
||||||
|
depth={0}
|
||||||
|
keyPath=""
|
||||||
|
emptyElementsText=""
|
||||||
|
/>
|
||||||
|
</JsonTreeContextProvider>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { JsonTree } from '@/workflow/components/json-visualizer/components/JsonTree';
|
|
||||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { JsonTree } from 'twenty-ui';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -15,6 +16,8 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
const workflowRunId = useWorkflowRunIdOrThrow();
|
const workflowRunId = useWorkflowRunIdOrThrow();
|
||||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||||
|
|
||||||
|
const { t } = useLingui();
|
||||||
|
|
||||||
if (!isDefined(workflowRun?.output?.stepsOutput)) {
|
if (!isDefined(workflowRun?.output?.stepsOutput)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -23,7 +26,13 @@ export const WorkflowRunStepOutputDetail = ({ stepId }: { stepId: string }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<JsonTree value={stepOutput} />
|
<JsonTree
|
||||||
|
value={stepOutput}
|
||||||
|
emptyArrayLabel={t`Empty Array`}
|
||||||
|
emptyObjectLabel={t`Empty Object`}
|
||||||
|
arrowButtonCollapsedLabel={t`Expand`}
|
||||||
|
arrowButtonExpandedLabel={t`Collapse`}
|
||||||
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export * from './components';
|
|||||||
export * from './display';
|
export * from './display';
|
||||||
export * from './feedback';
|
export * from './feedback';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
|
export * from './json-visualizer';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
export * from './navigation';
|
export * from './navigation';
|
||||||
export * from './testing';
|
export * from './testing';
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { JsonTree } from '@/workflow/components/json-visualizer/components/JsonTree';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import {
|
import {
|
||||||
expect,
|
expect,
|
||||||
@ -6,14 +5,18 @@ import {
|
|||||||
waitForElementToBeRemoved,
|
waitForElementToBeRemoved,
|
||||||
within,
|
within,
|
||||||
} from '@storybook/test';
|
} from '@storybook/test';
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
import { JsonTree } from '@ui/json-visualizer/components/JsonTree';
|
||||||
|
|
||||||
const meta: Meta<typeof JsonTree> = {
|
const meta: Meta<typeof JsonTree> = {
|
||||||
title: 'Modules/Workflow/JsonVisualizer/JsonTree',
|
title: 'UI/JsonVisualizer/JsonTree',
|
||||||
component: JsonTree,
|
component: JsonTree,
|
||||||
args: {},
|
args: {
|
||||||
|
emptyArrayLabel: 'Empty Array',
|
||||||
|
emptyObjectLabel: 'Empty Object',
|
||||||
|
arrowButtonCollapsedLabel: 'Expand',
|
||||||
|
arrowButtonExpandedLabel: 'Collapse',
|
||||||
|
},
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
decorators: [I18nFrontDecorator],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { JsonNestedNode } from '@/workflow/components/json-visualizer/components/JsonNestedNode';
|
import { IconBrackets } from '@ui/display';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { JsonNestedNode } from '@ui/json-visualizer/components/JsonNestedNode';
|
||||||
import { IconBrackets } from 'twenty-ui';
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||||
import { JsonArray } from 'type-fest';
|
import { JsonArray } from 'type-fest';
|
||||||
|
|
||||||
export const JsonArrayNode = ({
|
export const JsonArrayNode = ({
|
||||||
@ -8,15 +8,13 @@ export const JsonArrayNode = ({
|
|||||||
value,
|
value,
|
||||||
depth,
|
depth,
|
||||||
keyPath,
|
keyPath,
|
||||||
shouldHighlightNode,
|
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
label?: string;
|
||||||
value: JsonArray;
|
value: JsonArray;
|
||||||
depth: number;
|
depth: number;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLingui();
|
const { emptyArrayLabel } = useJsonTreeContextOrThrow();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JsonNestedNode
|
<JsonNestedNode
|
||||||
@ -29,9 +27,8 @@ export const JsonArrayNode = ({
|
|||||||
label={label}
|
label={label}
|
||||||
Icon={IconBrackets}
|
Icon={IconBrackets}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
emptyElementsText={t`Empty Array`}
|
emptyElementsText={emptyArrayLabel}
|
||||||
keyPath={keyPath}
|
keyPath={keyPath}
|
||||||
shouldHighlightNode={shouldHighlightNode}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1,12 +1,12 @@
|
|||||||
import { JsonArrow } from '@/workflow/components/json-visualizer/components/internal/JsonArrow';
|
|
||||||
import { JsonList } from '@/workflow/components/json-visualizer/components/internal/JsonList';
|
|
||||||
import { JsonNodeLabel } from '@/workflow/components/json-visualizer/components/internal/JsonNodeLabel';
|
|
||||||
import { JsonNode } from '@/workflow/components/json-visualizer/components/JsonNode';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
import { IconComponent } from '@ui/display';
|
||||||
|
import { JsonArrow } from '@ui/json-visualizer/components/internal/JsonArrow';
|
||||||
|
import { JsonList } from '@ui/json-visualizer/components/internal/JsonList';
|
||||||
|
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
||||||
|
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { IconComponent } from 'twenty-ui';
|
|
||||||
import { JsonValue } from 'type-fest';
|
import { JsonValue } from 'type-fest';
|
||||||
|
|
||||||
const StyledContainer = styled.li`
|
const StyledContainer = styled.li`
|
||||||
@ -37,7 +37,6 @@ export const JsonNestedNode = ({
|
|||||||
emptyElementsText,
|
emptyElementsText,
|
||||||
depth,
|
depth,
|
||||||
keyPath,
|
keyPath,
|
||||||
shouldHighlightNode,
|
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
label?: string;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
@ -46,7 +45,6 @@ export const JsonNestedNode = ({
|
|||||||
emptyElementsText: string;
|
emptyElementsText: string;
|
||||||
depth: number;
|
depth: number;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const hideRoot = !isDefined(label);
|
const hideRoot = !isDefined(label);
|
||||||
|
|
||||||
@ -69,7 +67,6 @@ export const JsonNestedNode = ({
|
|||||||
value={value}
|
value={value}
|
||||||
depth={depth + 1}
|
depth={depth + 1}
|
||||||
keyPath={nextKeyPath}
|
keyPath={nextKeyPath}
|
||||||
shouldHighlightNode={shouldHighlightNode}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -1,14 +1,15 @@
|
|||||||
import { JsonArrayNode } from '@/workflow/components/json-visualizer/components/JsonArrayNode';
|
|
||||||
import { JsonObjectNode } from '@/workflow/components/json-visualizer/components/JsonObjectNode';
|
|
||||||
import { JsonValueNode } from '@/workflow/components/json-visualizer/components/JsonValueNode';
|
|
||||||
import { isArray } from '@/workflow/components/json-visualizer/utils/isArray';
|
|
||||||
import { isBoolean, isNull, isNumber, isString } from '@sniptt/guards';
|
import { isBoolean, isNull, isNumber, isString } from '@sniptt/guards';
|
||||||
import {
|
import {
|
||||||
IconCheckbox,
|
IconCheckbox,
|
||||||
IconCircleOff,
|
IconCircleOff,
|
||||||
IconNumber9,
|
IconNumber9,
|
||||||
IconTypography,
|
IconTypography,
|
||||||
} from 'twenty-ui';
|
} from '@ui/display';
|
||||||
|
import { JsonArrayNode } from '@ui/json-visualizer/components/JsonArrayNode';
|
||||||
|
import { JsonObjectNode } from '@ui/json-visualizer/components/JsonObjectNode';
|
||||||
|
import { JsonValueNode } from '@ui/json-visualizer/components/JsonValueNode';
|
||||||
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||||
|
import { isArray } from '@ui/json-visualizer/utils/isArray';
|
||||||
import { JsonValue } from 'type-fest';
|
import { JsonValue } from 'type-fest';
|
||||||
|
|
||||||
export const JsonNode = ({
|
export const JsonNode = ({
|
||||||
@ -16,14 +17,14 @@ export const JsonNode = ({
|
|||||||
value,
|
value,
|
||||||
depth,
|
depth,
|
||||||
keyPath,
|
keyPath,
|
||||||
shouldHighlightNode,
|
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
label?: string;
|
||||||
value: JsonValue;
|
value: JsonValue;
|
||||||
depth: number;
|
depth: number;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const { shouldHighlightNode } = useJsonTreeContextOrThrow();
|
||||||
|
|
||||||
const isHighlighted = shouldHighlightNode?.(keyPath) ?? false;
|
const isHighlighted = shouldHighlightNode?.(keyPath) ?? false;
|
||||||
|
|
||||||
if (isNull(value)) {
|
if (isNull(value)) {
|
||||||
@ -77,7 +78,6 @@ export const JsonNode = ({
|
|||||||
value={value}
|
value={value}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
keyPath={keyPath}
|
keyPath={keyPath}
|
||||||
shouldHighlightNode={shouldHighlightNode}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -88,7 +88,6 @@ export const JsonNode = ({
|
|||||||
value={value}
|
value={value}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
keyPath={keyPath}
|
keyPath={keyPath}
|
||||||
shouldHighlightNode={shouldHighlightNode}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { JsonNestedNode } from '@/workflow/components/json-visualizer/components/JsonNestedNode';
|
import { IconCube } from '@ui/display';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { JsonNestedNode } from '@ui/json-visualizer/components/JsonNestedNode';
|
||||||
import { IconCube } from 'twenty-ui';
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||||
import { JsonObject } from 'type-fest';
|
import { JsonObject } from 'type-fest';
|
||||||
|
|
||||||
export const JsonObjectNode = ({
|
export const JsonObjectNode = ({
|
||||||
@ -8,15 +8,13 @@ export const JsonObjectNode = ({
|
|||||||
value,
|
value,
|
||||||
depth,
|
depth,
|
||||||
keyPath,
|
keyPath,
|
||||||
shouldHighlightNode,
|
|
||||||
}: {
|
}: {
|
||||||
label?: string;
|
label?: string;
|
||||||
value: JsonObject;
|
value: JsonObject;
|
||||||
depth: number;
|
depth: number;
|
||||||
keyPath: string;
|
keyPath: string;
|
||||||
shouldHighlightNode?: (keyPath: string) => boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLingui();
|
const { emptyObjectLabel } = useJsonTreeContextOrThrow();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JsonNestedNode
|
<JsonNestedNode
|
||||||
@ -29,9 +27,8 @@ export const JsonObjectNode = ({
|
|||||||
label={label}
|
label={label}
|
||||||
Icon={IconCube}
|
Icon={IconCube}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
emptyElementsText={t`Empty Object`}
|
emptyElementsText={emptyObjectLabel}
|
||||||
keyPath={keyPath}
|
keyPath={keyPath}
|
||||||
shouldHighlightNode={shouldHighlightNode}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { JsonList } from '@ui/json-visualizer/components/internal/JsonList';
|
||||||
|
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
||||||
|
import { JsonTreeContextProvider } from '@ui/json-visualizer/components/JsonTreeContextProvider';
|
||||||
|
import { JsonValue } from 'type-fest';
|
||||||
|
|
||||||
|
export const JsonTree = ({
|
||||||
|
value,
|
||||||
|
shouldHighlightNode,
|
||||||
|
emptyArrayLabel,
|
||||||
|
emptyObjectLabel,
|
||||||
|
arrowButtonCollapsedLabel,
|
||||||
|
arrowButtonExpandedLabel,
|
||||||
|
}: {
|
||||||
|
value: JsonValue;
|
||||||
|
shouldHighlightNode?: (keyPath: string) => boolean;
|
||||||
|
emptyArrayLabel: string;
|
||||||
|
emptyObjectLabel: string;
|
||||||
|
arrowButtonCollapsedLabel: string;
|
||||||
|
arrowButtonExpandedLabel: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<JsonTreeContextProvider
|
||||||
|
value={{
|
||||||
|
shouldHighlightNode,
|
||||||
|
emptyArrayLabel,
|
||||||
|
emptyObjectLabel,
|
||||||
|
arrowButtonCollapsedLabel,
|
||||||
|
arrowButtonExpandedLabel,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<JsonList depth={0}>
|
||||||
|
<JsonNode value={value} depth={0} keyPath="" />
|
||||||
|
</JsonList>
|
||||||
|
</JsonTreeContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import {
|
||||||
|
JsonTreeContext,
|
||||||
|
JsonTreeContextType,
|
||||||
|
} from '@ui/json-visualizer/contexts/JsonTreeContext';
|
||||||
|
|
||||||
|
export const JsonTreeContextProvider = ({
|
||||||
|
value,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
value: JsonTreeContextType;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<JsonTreeContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</JsonTreeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { JsonListItem } from '@/workflow/components/json-visualizer/components/internal/JsonListItem';
|
|
||||||
import { JsonNodeLabel } from '@/workflow/components/json-visualizer/components/internal/JsonNodeLabel';
|
|
||||||
import { JsonNodeValue } from '@/workflow/components/json-visualizer/components/internal/JsonNodeValue';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from '@ui/display';
|
||||||
|
import { JsonListItem } from '@ui/json-visualizer/components/internal/JsonListItem';
|
||||||
|
import { JsonNodeLabel } from '@ui/json-visualizer/components/internal/JsonNodeLabel';
|
||||||
|
import { JsonNodeValue } from '@ui/json-visualizer/components/internal/JsonNodeValue';
|
||||||
|
|
||||||
const StyledListItem = styled(JsonListItem)`
|
const StyledListItem = styled(JsonListItem)`
|
||||||
column-gap: ${({ theme }) => theme.spacing(2)};
|
column-gap: ${({ theme }) => theme.spacing(2)};
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { VisibilityHidden } from '@ui/accessibility';
|
||||||
|
import { IconChevronDown } from '@ui/display';
|
||||||
|
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { IconChevronDown, VisibilityHidden } from 'twenty-ui';
|
|
||||||
|
|
||||||
const StyledButton = styled(motion.button)`
|
const StyledButton = styled(motion.button)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -29,13 +30,16 @@ export const JsonArrow = ({
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLingui();
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { arrowButtonCollapsedLabel, arrowButtonExpandedLabel } =
|
||||||
|
useJsonTreeContextOrThrow();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledButton onClick={onClick}>
|
<StyledButton onClick={onClick}>
|
||||||
<VisibilityHidden>{isOpen ? t`Collapse` : t`Expand`}</VisibilityHidden>
|
<VisibilityHidden>
|
||||||
|
{isOpen ? arrowButtonExpandedLabel : arrowButtonCollapsedLabel}
|
||||||
|
</VisibilityHidden>
|
||||||
|
|
||||||
<MotionIconChevronDown
|
<MotionIconChevronDown
|
||||||
size={theme.icon.size.md}
|
size={theme.icon.size.md}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconComponent } from 'twenty-ui';
|
import { IconComponent } from '@ui/display';
|
||||||
|
|
||||||
const StyledLabelContainer = styled.span<{ isHighlighted?: boolean }>`
|
const StyledLabelContainer = styled.span<{ isHighlighted?: boolean }>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export type JsonTreeContextType = {
|
||||||
|
shouldHighlightNode?: (keyPath: string) => boolean;
|
||||||
|
emptyArrayLabel: string;
|
||||||
|
emptyObjectLabel: string;
|
||||||
|
arrowButtonCollapsedLabel: string;
|
||||||
|
arrowButtonExpandedLabel: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const JsonTreeContext = createContext<JsonTreeContextType | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { JsonTreeContext } from '@ui/json-visualizer/contexts/JsonTreeContext';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
|
export const useJsonTreeContextOrThrow = () => {
|
||||||
|
const value = useContext(JsonTreeContext);
|
||||||
|
|
||||||
|
if (!isDefined(value)) {
|
||||||
|
throw new Error(
|
||||||
|
'useJsonTreeContextOrThrow must be used within a JsonTreeContextProvider',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
10
packages/twenty-ui/src/json-visualizer/index.ts
Normal file
10
packages/twenty-ui/src/json-visualizer/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export * from './components/JsonArrayNode';
|
||||||
|
export * from './components/JsonNestedNode';
|
||||||
|
export * from './components/JsonNode';
|
||||||
|
export * from './components/JsonObjectNode';
|
||||||
|
export * from './components/JsonTree';
|
||||||
|
export * from './components/JsonTreeContextProvider';
|
||||||
|
export * from './components/JsonValueNode';
|
||||||
|
export * from './contexts/JsonTreeContext';
|
||||||
|
export * from './hooks/useJsonTreeContextOrThrow';
|
||||||
|
export * from './utils/isArray';
|
||||||
Reference in New Issue
Block a user