Skip to content
Merged
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
Expand Up @@ -8,6 +8,9 @@ const AddMoreIcon = importRemixIcon(
const AddMoreFillIcon = importRemixIcon(
async () => import("remixicon-react/AddCircleFillIcon"),
);
const ArrowGoForwardLineIcon = importRemixIcon(
async () => import("remixicon-react/ArrowGoForwardLineIcon"),
);
const ArrowGoBackLineIcon = importRemixIcon(
async () => import("remixicon-react/ArrowGoBackLineIcon"),
);
Expand Down Expand Up @@ -1178,6 +1181,7 @@ const ICON_LOOKUP = {
"add-line": AddLineIcon,
"add-more": AddMoreIcon,
"add-more-fill": AddMoreFillIcon,
"arrow-go-forward": ArrowGoForwardLineIcon,
"alert-fill": AlertFillIcon,
"alert-line": AlertLineIcon,
"align-center": AlignCenter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const _Sidebar = (props: SidebarProps, ref: Ref<HTMLDivElement>) => {
children,
className,
collapsible = "offcanvas",
extraTitleButton,
onEnter: onEnterProp,
onEntered: onEnteredProp,
onExit: onExitProp,
Expand Down Expand Up @@ -47,7 +48,7 @@ const _Sidebar = (props: SidebarProps, ref: Ref<HTMLDivElement>) => {
};

const content = (
<SidebarContent title={title}>
<SidebarContent extraTitleButton={extraTitleButton} title={title}>
{typeof children === "function"
? children({ isAnimating, state })
: children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import clsx from "clsx";
import React, { type Ref } from "react";
import React, { type ReactNode, type Ref } from "react";

import { Flex } from "../../Flex";
import { Text } from "../../Text";
Expand All @@ -9,6 +9,7 @@ import { useSidebar } from "./use-sidebar";

interface SidebarContentProps {
title?: string;
extraTitleButton?: ReactNode;
className?: string;
children: React.ReactNode;
}
Expand All @@ -17,7 +18,7 @@ const _SidebarContent = (
props: SidebarContentProps,
ref: Ref<HTMLDivElement>,
) => {
const { children, className, title, ...rest } = props;
const { children, className, extraTitleButton, title, ...rest } = props;
const { isMobile, setState, state } = useSidebar();

return (
Expand All @@ -39,26 +40,29 @@ const _SidebarContent = (
className={styles.sidebarTitle}
fontWeight={500}
lineClamp={1}
textAlign="center"
>
{title}
</Text>
)}
{!isMobile && (
<Button
className={styles.sidebarHeaderExpandButton}
color="neutral"
icon={
state === "full-width"
? "arrows-diagonal-minimize"
: "arrows-diagonal-2"
}
onPress={() =>
setState(state === "full-width" ? "expanded" : "full-width")
}
variant="ghost"
/>
)}

<Flex>
{extraTitleButton}
{!isMobile && (
<Button
className={styles.sidebarHeaderExpandButton}
color="neutral"
icon={
state === "full-width"
? "arrows-diagonal-minimize"
: "arrows-diagonal-2"
}
onPress={() =>
setState(state === "full-width" ? "expanded" : "full-width")
}
variant="ghost"
/>
)}
</Flex>
</Flex>
<div className={styles.sidebarContentInner}>{children}</div>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,7 @@
}

.sidebarTitle {
position: absolute;
left: 0;
right: 0;
margin: 0 var(--sizing-14);
margin: 0 var(--sizing-4);
}

.sidebarHeaderExpandButton {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface SidebarProviderProps {
export interface SidebarProps {
variant?: "sidebar" | "floating" | "inset";
collapsible?: "offcanvas" | "icon" | "none";
extraTitleButton?: ReactNode;
onEnter?: () => void;
onExit?: () => void;
onEntered?: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Button, Flex } from "@appsmith/ads";
import type { ActionResponse } from "api/ActionAPI";
import { type Action } from "entities/Action";
import React, { useState } from "react";
import { parseActionResponse } from "../Response/utils";
import { EmptyVisualization } from "./components/EmptyVisualization";
import { LoadingOverlay } from "./components/LoadingOverlay";
import { PromptInput } from "./components/PromptInput";
import { Result } from "./components/Result";
import { SuggestionButtons } from "./components/SuggestionButtons";
import { useGenerateVisualization } from "./useGenerateVisualization";
import { useSaveVisualization } from "./useSaveVisualization";
import { ErrorBoundary } from "@sentry/react";

interface VisualizationProps {
action: Action;
actionResponse?: ActionResponse;
}

const BOTTOM_BAR_HEIGHT = 37;

export const Visualization = (props: VisualizationProps) => {
const { action, actionResponse } = props;
const [prompt, setPrompt] = useState("");
const { response } = parseActionResponse(actionResponse);
const generateVisualization = useGenerateVisualization(
action.id,
action.visualization?.result,
);
const saveVisualization = useSaveVisualization(action.id);

return (
// TODO: Remove the hardcoded height
<Flex
flexDirection="column"
height={`calc(100% - ${BOTTOM_BAR_HEIGHT}px)`}
position="relative"
>
<Flex
borderBottom="1px solid var(--ads-v2-color-border-muted)"
flexDirection="column"
gap="spaces-3"
padding="spaces-3"
>
<SuggestionButtons onApply={setPrompt} />
<Flex gap="spaces-3">
<PromptInput
isDisabled={!response || saveVisualization.isLoading}
isLoading={generateVisualization.isLoading}
onChange={setPrompt}
onSubmit={async () =>
generateVisualization.execute(prompt, response)
}
value={prompt}
/>
<Button
isDisabled={
generateVisualization.isLoading ||
saveVisualization.isLoading ||
!generateVisualization.hasPrevious
}
isIconButton
kind="secondary"
onClick={generateVisualization.previous}
size="md"
startIcon="arrow-go-back"
/>
<Button
isDisabled={
generateVisualization.isLoading ||
saveVisualization.isLoading ||
!generateVisualization.hasNext
}
isIconButton
kind="secondary"
onClick={generateVisualization.next}
size="md"
startIcon="arrow-go-forward"
/>
<Button
isDisabled={
generateVisualization.isLoading ||
saveVisualization.isLoading ||
!generateVisualization.elements
}
isLoading={saveVisualization.isLoading}
kind="secondary"
onClick={async () =>
generateVisualization.elements &&
saveVisualization.execute(generateVisualization.elements)
}
size="md"
>
Save
</Button>
</Flex>
</Flex>

<Flex flexDirection="column" flexGrow={1} position="relative">
{generateVisualization.elements ? (
<ErrorBoundary fallback="Visualization failed. Please try again.">
<Result data={response} elements={generateVisualization.elements} />
</ErrorBoundary>
) : (
<EmptyVisualization />
)}
{generateVisualization.isLoading && <LoadingOverlay />}
</Flex>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Flex, Text } from "@appsmith/ads";
import NoVisualizationSVG from "assets/images/no-visualization.svg";
import React from "react";

export const EmptyVisualization = () => {
return (
<Flex
alignItems="center"
flexDirection="column"
gap="spaces-7"
height="100%"
justifyContent="center"
>
<img alt="No visualization" src={NoVisualizationSVG} />
<Text>The response visualization will be shown here</Text>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Flex, Text } from "@appsmith/ads";
import React from "react";

export const LoadingOverlay = () => {
return (
<Flex
alignItems="center"
backgroundColor="color-mix(in srgb, var(--ads-v2-color-gray-800) 80%, transparent);"
bottom="0"
flexDirection="column"
justifyContent="center"
left="0"
position="absolute"
right="0"
top="0"
>
<Text color="var(--ads-v2-color-white)" kind="heading-m">
Generating visualization...
</Text>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Button, Input } from "@appsmith/ads";
import React from "react";
import styled from "styled-components";

interface PromptInputProps {
value: string;
onChange: (value: string) => void;
onSubmit: () => void;
isLoading: boolean;
isDisabled: boolean;
}

const PromptForm = styled.form`
display: flex;
flex: 1;
gap: var(--ads-v2-spaces-3);
`;

export const PromptInput = (props: PromptInputProps) => {
const { isDisabled, isLoading, onChange, onSubmit, value } = props;

return (
<PromptForm
onSubmit={(e) => {
e.preventDefault();
onSubmit();
}}
>
<Input
isDisabled={isDisabled}
onChange={onChange}
placeholder="Describe the data visualisation you want"
size="md"
value={value}
/>
<Button
isDisabled={isDisabled || !value}
isIconButton
isLoading={isLoading}
kind="primary"
size="md"
startIcon="arrow-up-line"
type="submit"
/>
</PromptForm>
);
};
Loading