diff --git a/app/client/.storybook/configs/contexts.js b/app/client/.storybook/configs/contexts.js index 5f6a61ddce..00c5acb548 100644 --- a/app/client/.storybook/configs/contexts.js +++ b/app/client/.storybook/configs/contexts.js @@ -1,10 +1,36 @@ import { ThemeProvider, theme } from "../../src/constants/DefaultTheme"; +import { light, dark } from "constants/DefaultTheme"; export const contexts = [ - { - icon: "box", - title: "Themes", - components: [ThemeProvider], - params: [{ name: "default", props: { theme: theme } }], - }, -]; \ No newline at end of file + { + icon: "box", + title: "Themes", + components: [ThemeProvider], + params: [ + { + name: "lightTheme", + props: { + theme: { + ...theme, + colors: { + ...theme.colors, + ...light, + }, + }, + }, + }, + { + name: "darkTheme", + props: { + theme: { + ...theme, + colors: { + ...theme.colors, + ...dark, + }, + }, + }, + }, + ], + }, +]; diff --git a/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/CreateSameAppInDiffOrg_spec.js b/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/CreateSameAppInDiffOrg_spec.js index 86f306ae5a..3de68cf260 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/CreateSameAppInDiffOrg_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/CreateSameAppInDiffOrg_spec.js @@ -1,6 +1,6 @@ /// -describe("Create app same name in differnt org", function() { +describe("Create app same name in different org", function() { let orgid; let appid; @@ -26,6 +26,8 @@ describe("Create app same name in differnt org", function() { 200, ); cy.reload(); - cy.CreateApp(appid); + const newOrgName = orgid + "1"; + cy.createOrg(newOrgName); + cy.CreateAppForOrg(newOrgName, appid); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/Pages/Page_Load_Spec.js b/app/client/cypress/integration/Smoke_TestSuite/Pages/Page_Load_Spec.js index 583fbe9fcd..d8b75a5f9f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Pages/Page_Load_Spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Pages/Page_Load_Spec.js @@ -21,6 +21,8 @@ describe("Page Load tests", () => { cy.get(".t--page-switch-tab") .contains("Page2") .parent() + .parent() + .parent() .should("have.class", "is-active"); // Assert active page DSL cy.get(commonlocators.headingTextStyle).should( @@ -33,6 +35,8 @@ describe("Page Load tests", () => { cy.get(".t--page-switch-tab") .contains("Page2") .parent() + .parent() + .parent() .should("have.class", "is-active"); // Assert active page DSL cy.get(commonlocators.headingTextStyle).should( @@ -47,6 +51,8 @@ describe("Page Load tests", () => { cy.get(".t--page-switch-tab") .contains("Page1") .parent() + .parent() + .parent() .should("have.class", "is-active"); // Assert active page DSL cy.get(commonlocators.headingTextStyle).should( diff --git a/app/client/cypress/manual_TestSuite/Invite_flow_Spec.js b/app/client/cypress/manual_TestSuite/Invite_flow_Spec.js index 96317cbc87..8508607a80 100644 --- a/app/client/cypress/manual_TestSuite/Invite_flow_Spec.js +++ b/app/client/cypress/manual_TestSuite/Invite_flow_Spec.js @@ -61,4 +61,4 @@ describe("adding role without Email Id", function() { } ) } -) \ No newline at end of file +) diff --git a/app/client/cypress/manual_TestSuite/Login_Spec.js b/app/client/cypress/manual_TestSuite/Login_Spec.js index c85a1355ea..1d2a30bce2 100644 --- a/app/client/cypress/manual_TestSuite/Login_Spec.js +++ b/app/client/cypress/manual_TestSuite/Login_Spec.js @@ -31,4 +31,4 @@ describe("Onboarding flow", function() } ) } -) \ No newline at end of file +) diff --git a/app/client/cypress/manual_TestSuite/Organisation_Name_Spec.js b/app/client/cypress/manual_TestSuite/Organisation_Name_Spec.js index 9eed15193e..058eb88cd4 100644 --- a/app/client/cypress/manual_TestSuite/Organisation_Name_Spec.js +++ b/app/client/cypress/manual_TestSuite/Organisation_Name_Spec.js @@ -74,4 +74,4 @@ describe("Checking for error message on Organisation Name ", function() { } ) } -) \ No newline at end of file +) diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index c392ce80db..491d2d881d 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -239,7 +239,7 @@ Cypress.Commands.add("CreateAppForOrg", (orgName, appname) => { ); }); -Cypress.Commands.add("CreateApp", (appname) => { +Cypress.Commands.add("CreateAppInFirstListedOrg", (appname) => { cy.get(homePage.createNew) .first() .click({ force: true }); diff --git a/app/client/cypress/support/index.js b/app/client/cypress/support/index.js index 35f0fd6d5d..938d875967 100644 --- a/app/client/cypress/support/index.js +++ b/app/client/cypress/support/index.js @@ -49,7 +49,7 @@ before(function() { cy.generateUUID().then((id) => { appId = id; - cy.CreateApp(id); + cy.CreateAppInFirstListedOrg(id); localStorage.setItem("AppName", appId); }); diff --git a/app/client/src/actions/applicationActions.ts b/app/client/src/actions/applicationActions.ts index e6f8948c18..83458a9ff0 100644 --- a/app/client/src/actions/applicationActions.ts +++ b/app/client/src/actions/applicationActions.ts @@ -69,3 +69,9 @@ export const getAllApplications = () => { type: ReduxActionTypes.GET_ALL_APPLICATION_INIT, }; }; + +export const resetCurrentApplication = () => { + return { + type: ReduxActionTypes.RESET_CURRENT_APPLICATION, + }; +}; diff --git a/app/client/src/assets/icons/ads/arrow-left.svg b/app/client/src/assets/icons/ads/arrow-left.svg new file mode 100644 index 0000000000..016b212df5 --- /dev/null +++ b/app/client/src/assets/icons/ads/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/ads/chevron_left.svg b/app/client/src/assets/icons/ads/chevron_left.svg new file mode 100644 index 0000000000..2dfca379fe --- /dev/null +++ b/app/client/src/assets/icons/ads/chevron_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/ads/chevron_right.svg b/app/client/src/assets/icons/ads/chevron_right.svg new file mode 100644 index 0000000000..2ee5421219 --- /dev/null +++ b/app/client/src/assets/icons/ads/chevron_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/ads/fork.svg b/app/client/src/assets/icons/ads/fork.svg new file mode 100644 index 0000000000..c2f9c94ecc --- /dev/null +++ b/app/client/src/assets/icons/ads/fork.svg @@ -0,0 +1 @@ +fork \ No newline at end of file diff --git a/app/client/src/assets/images/appsmith_logo_square.png b/app/client/src/assets/images/appsmith_logo_square.png new file mode 100644 index 0000000000..f0ff69ce8b Binary files /dev/null and b/app/client/src/assets/images/appsmith_logo_square.png differ diff --git a/app/client/src/components/ads/AppIcon.tsx b/app/client/src/components/ads/AppIcon.tsx index da69df2d7e..6cb825ca35 100644 --- a/app/client/src/components/ads/AppIcon.tsx +++ b/app/client/src/components/ads/AppIcon.tsx @@ -193,6 +193,11 @@ const appSizeHandler = (size: Size): cssAttributes => { height = 50; padding = 50; break; + default: + width = 20; + height = 20; + padding = 5; + break; } return { width, height, padding }; }; diff --git a/app/client/src/components/ads/Button.tsx b/app/client/src/components/ads/Button.tsx index c49e083978..aa07d126ae 100644 --- a/app/client/src/components/ads/Button.tsx +++ b/app/client/src/components/ads/Button.tsx @@ -18,6 +18,8 @@ export enum Category { } export enum Size { + xxs = "xxs", + xs = "xs", small = "small", medium = "medium", large = "large", @@ -59,128 +61,148 @@ type ButtonProps = CommonComponentProps & { href?: string; tag?: "a" | "button"; type?: "submit" | "reset" | "button"; + target?: string; +}; + +const defaultProps = { + category: Category.primary, + variant: Variant.info, + size: Size.small, + isLoading: false, + disabled: false, + fill: false, + tag: "a", +}; + +const getDisabledStyles = (props: ThemeProp & ButtonProps) => { + const variant = props.variant || defaultProps.variant; + const category = props.category || defaultProps.category; + + const stylesByCategory = { + [Category.primary]: { + txtColorPrimary: props.theme.colors.button.disabledText, + bgColorPrimary: props.theme.colors[variant].darkest, + borderColorPrimary: props.theme.colors[variant].darkest, + }, + [Category.secondary]: { + txtColorSecondary: props.theme.colors.button.disabledText, + bgColorSecondary: props.theme.colors[variant].darkest, + borderColorSecondary: props.theme.colors[variant].darker, + }, + [Category.tertiary]: { + txtColorTertiary: props.theme.colors.button.disabledText, + bgColorTertiary: props.theme.colors.tertiary.darker, + borderColorTertiary: props.theme.colors.tertiary.dark, + }, + }; + + return stylesByCategory[category]; +}; + +const getMainStateStyles = (props: ThemeProp & ButtonProps) => { + const variant = props.variant || defaultProps.variant; + const category = props.category || defaultProps.category; + + const stylesByCategory = { + [Category.primary]: { + bgColorPrimary: props.theme.colors[variant].main, + borderColorPrimary: props.theme.colors[variant].main, + txtColorPrimary: "#fff", + }, + [Category.secondary]: { + borderColorSecondary: props.theme.colors[variant].main, + txtColorSecondary: props.theme.colors[variant].main, + bgColorSecondary: "transparent", + }, + [Category.tertiary]: { + bgColorTertiary: "transparent", + borderColorTertiary: props.theme.colors.tertiary.main, + txtColorTertiary: props.theme.colors.tertiary.main, + }, + }; + + return stylesByCategory[category]; +}; + +const getHoverStateStyles = (props: ThemeProp & ButtonProps) => { + const variant = props.variant || defaultProps.variant; + const category = props.category || defaultProps.category; + + const stylesByCategory = { + [Category.primary]: { + bgColorPrimary: props.theme.colors[variant].dark, + borderColorPrimary: props.theme.colors[variant].dark, + txtColorPrimary: "#fff", + }, + [Category.secondary]: { + bgColorSecondary: hexToRgba(props.theme.colors[variant].main, 0.1), + txtColorSecondary: props.theme.colors[variant].main, + borderColorSecondary: props.theme.colors[variant].main, + }, + [Category.tertiary]: { + bgColorTertiary: hexToRgba(props.theme.colors.tertiary.main, 0.1), + borderColorTertiary: props.theme.colors.tertiary.main, + txtColorTertiary: props.theme.colors.tertiary.main, + }, + }; + + return stylesByCategory[category]; +}; + +const getActiveStateStyles = (props: ThemeProp & ButtonProps) => { + const variant = props.variant || defaultProps.variant; + const category = props.category || defaultProps.category; + + const stylesByCategory = { + [Category.primary]: { + bgColorPrimary: props.theme.colors[variant].dark, + borderColorPrimary: props.theme.colors[variant].main, + txtColorPrimary: "#fff", + }, + [Category.secondary]: { + bgColorSecondary: hexToRgba(props.theme.colors[variant].main, 0.1), + txtColorSecondary: props.theme.colors[variant].light, + borderColorSecondary: props.theme.colors[variant].light, + }, + [Category.tertiary]: { + bgColorTertiary: hexToRgba(props.theme.colors.tertiary.main, 0.1), + borderColorTertiary: props.theme.colors.tertiary.light, + txtColorTertiary: props.theme.colors.tertiary.light, + }, + }; + + return stylesByCategory[category]; }; const stateStyles = ( props: ThemeProp & ButtonProps, - state: string, + stateArg: string, ): stateStyleType => { - let bgColorPrimary, - borderColorPrimary, - txtColorPrimary, - bgColorSecondary, - borderColorSecondary, - txtColorSecondary, - bgColorTertiary, - borderColorTertiary, - txtColorTertiary; - - if (props.isLoading || props.disabled) { - switch (props.category) { - case Category.primary: - if (props.variant) { - bgColorPrimary = props.theme.colors[props.variant].darkest; - borderColorPrimary = props.theme.colors[props.variant].darkest; - } - txtColorPrimary = props.theme.colors.button.disabledText; - break; - case Category.secondary: - if (props.variant) { - bgColorSecondary = props.theme.colors[props.variant].darkest; - borderColorSecondary = props.theme.colors[props.variant].darker; - } - txtColorSecondary = props.theme.colors.button.disabledText; - break; - case Category.tertiary: - bgColorTertiary = props.theme.colors.tertiary.darker; - borderColorTertiary = props.theme.colors.tertiary.dark; - txtColorTertiary = props.theme.colors.button.disabledText; - break; - } - } else if (state === "main") { - switch (props.category) { - case Category.primary: - if (props.variant) { - bgColorPrimary = props.theme.colors[props.variant].main; - borderColorPrimary = props.theme.colors[props.variant].main; - } - txtColorPrimary = "#fff"; - break; - case Category.secondary: - if (props.variant) { - borderColorSecondary = props.theme.colors[props.variant].main; - txtColorSecondary = props.theme.colors[props.variant].main; - } - bgColorSecondary = "transparent"; - break; - case Category.tertiary: - bgColorTertiary = "transparent"; - borderColorTertiary = props.theme.colors.tertiary.main; - txtColorTertiary = props.theme.colors.tertiary.main; - break; - } - } else if (state === "hover") { - switch (props.category) { - case Category.primary: - if (props.variant) { - bgColorPrimary = props.theme.colors[props.variant].dark; - borderColorPrimary = props.theme.colors[props.variant].dark; - } - txtColorPrimary = "#fff"; - break; - case Category.secondary: - if (props.variant) { - bgColorSecondary = hexToRgba( - props.theme.colors[props.variant].main, - 0.1, - ); - txtColorSecondary = props.theme.colors[props.variant].main; - borderColorSecondary = props.theme.colors[props.variant].main; - } - break; - case Category.tertiary: - bgColorTertiary = hexToRgba(props.theme.colors.tertiary.main, 0.1); - borderColorTertiary = props.theme.colors.tertiary.main; - txtColorTertiary = props.theme.colors.tertiary.main; - break; - } - } else if (state === "active") { - switch (props.category) { - case Category.primary: - if (props.variant) { - bgColorPrimary = props.theme.colors[props.variant].dark; - borderColorPrimary = props.theme.colors[props.variant].main; - } - txtColorPrimary = "#fff"; - break; - case Category.secondary: - if (props.variant) { - bgColorSecondary = hexToRgba( - props.theme.colors[props.variant].main, - 0.1, - ); - txtColorSecondary = props.theme.colors[props.variant].light; - borderColorSecondary = props.theme.colors[props.variant].light; - } - break; - case Category.tertiary: - bgColorTertiary = hexToRgba(props.theme.colors.tertiary.main, 0.1); - borderColorTertiary = props.theme.colors.tertiary.light; - txtColorTertiary = props.theme.colors.tertiary.light; - break; - } - } + const styles = { + bgColorPrimary: "", + borderColorPrimary: "", + txtColorPrimary: "", + bgColorSecondary: "", + borderColorSecondary: "", + txtColorSecondary: "", + bgColorTertiary: "", + borderColorTertiary: "", + txtColorTertiary: "", + }; + const state = + props.isLoading || props.disabled + ? "disabled" + : (stateArg as keyof typeof stylesByState); + const stylesByState = { + disabled: getDisabledStyles(props), + main: getMainStateStyles(props), + hover: getHoverStateStyles(props), + active: getActiveStateStyles(props), + }; return { - bgColorPrimary, - borderColorPrimary, - txtColorPrimary, - bgColorSecondary, - borderColorSecondary, - txtColorSecondary, - bgColorTertiary, - borderColorTertiary, - txtColorTertiary, + ...styles, + ...stylesByState[state], }; }; @@ -211,36 +233,61 @@ const btnColorStyles = ( return { bgColor, txtColor, border }; }; +const getPaddingBySize = (props: ThemeProp & ButtonProps) => { + const paddingBySize = { + [Size.small]: `0px ${props.theme.spaces[3]}px`, + [Size.medium]: `0px ${props.theme.spaces[7]}px`, + [Size.large]: `0px ${props.theme.spaces[12] - 4}px`, + }; + const paddingBySizeForJustIcon = { + [Size.small]: `0px ${props.theme.spaces[1]}px`, + [Size.medium]: `0px ${props.theme.spaces[2]}px`, + [Size.large]: `0px ${props.theme.spaces[3]}px`, + }; + + const isIconOnly = !props.text && props.icon; + const paddingConfig = isIconOnly ? paddingBySizeForJustIcon : paddingBySize; + + const iSizeInConfig = + Object.keys(paddingConfig).indexOf(props.size || "") !== -1; + const size: any = props.size && iSizeInConfig ? props.size : Size.small; + + return paddingConfig[size as keyof typeof paddingConfig]; +}; + +const getHeightBySize = (props: ThemeProp & ButtonProps) => { + const heightBySize = { + [Size.small]: 20, + [Size.medium]: 30, + [Size.large]: 38, + }; + + const iSizeInConfig = + Object.keys(heightBySize).indexOf(props.size || "") !== -1; + const size: any = props.size && iSizeInConfig ? props.size : Size.small; + + return heightBySize[size as keyof typeof heightBySize]; +}; + +const getBtnFontBySize = (props: ThemeProp & ButtonProps) => { + const fontBySize = { + [Size.small]: smallButton, + [Size.medium]: mediumButton, + [Size.large]: largeButton, + }; + + const iSizeInConfig = + Object.keys(fontBySize).indexOf(props.size || "") !== -1; + const size: any = props.size && iSizeInConfig ? props.size : Size.small; + + return fontBySize[size as keyof typeof fontBySize]; +}; + const btnFontStyles = (props: ThemeProp & ButtonProps): BtnFontType => { - let buttonFont, - padding = "", - height = 0; - switch (props.size) { - case Size.small: - buttonFont = smallButton; - height = 20; - padding = - !props.text && props.icon - ? `0px ${props.theme.spaces[1]}px` - : `0px ${props.theme.spaces[3]}px`; - break; - case Size.medium: - buttonFont = mediumButton; - height = 30; - padding = - !props.text && props.icon - ? `0px ${props.theme.spaces[2]}px` - : `0px ${props.theme.spaces[7]}px`; - break; - case Size.large: - buttonFont = largeButton; - height = 38; - padding = - !props.text && props.icon - ? `0px ${props.theme.spaces[3]}px` - : `0px ${props.theme.spaces[12] - 4}px`; - break; - } + const padding = getPaddingBySize(props); + const height = getHeightBySize(props); + const buttonFont = getBtnFontBySize(props); + return { buttonFont, padding, height }; }; @@ -318,78 +365,85 @@ export const VisibilityWrapper = styled.div` `; const IconSizeProp = (size?: Size) => { - if (size === Size.small) { - return IconSize.SMALL; - } else if (size === Size.medium) { - return IconSize.MEDIUM; - } else if (size === Size.large) { - return IconSize.LARGE; - } else { - return IconSize.SMALL; - } + const sizeMapping = { + [Size.xxs]: IconSize.XXS, + [Size.xs]: IconSize.XS, + [Size.small]: IconSize.SMALL, + [Size.medium]: IconSize.MEDIUM, + [Size.large]: IconSize.LARGE, + }; + + return size ? sizeMapping[size] : IconSize.SMALL; }; -Button.defaultProps = { - category: Category.primary, - variant: Variant.info, - size: Size.small, - isLoading: false, - disabled: false, - fill: false, - tag: "a", -}; +const TextLoadingState = ({ text }: { text?: string }) => ( + {text} +); -function Button(props: ButtonProps) { - const IconLoadingState = ( - +const IconLoadingState = ({ size, icon }: { size?: Size; icon?: IconName }) => ( + +); + +const getIconContent = (props: ButtonProps) => + props.icon ? ( + props.isLoading ? ( + + ) : ( + + ) + ) : null; + +const getTextContent = (props: ButtonProps) => + props.text ? ( + props.isLoading ? ( + + ) : ( + props.text + ) + ) : null; + +const getButtonContent = (props: ButtonProps) => ( + <> + {getIconContent(props)} + {getTextContent(props)} + {props.isLoading ? : null} + +); + +const ButtonComponent = (props: ButtonProps) => ( + ) => + props.onClick && props.onClick(e) + } + > + {getButtonContent(props)} + +); + +const LinkButtonComponent = (props: ButtonProps) => ( + ) => + props.onClick && props.onClick(e) + } + > + {getButtonContent(props)} + +); + +const Button = (props: ButtonProps) => + props.tag === "button" ? ( + + ) : ( + ); - const TextLoadingState = {props.text}; - - const buttonContent = ( - <> - {props.icon ? ( - props.isLoading ? ( - IconLoadingState - ) : ( - - ) - ) : null} - - {props.text ? (props.isLoading ? TextLoadingState : props.text) : null} - - {props.isLoading ? : null} - - ); - - if (props.tag === "button") { - return ( - ) => - props.onClick && props.onClick(e) - } - > - {buttonContent} - - ); - } else { - return ( - ) => - props.onClick && props.onClick(e) - } - > - {buttonContent} - - ); - } -} - export default Button; + +Button.defaultProps = defaultProps; diff --git a/app/client/src/components/ads/Icon.tsx b/app/client/src/components/ads/Icon.tsx index 34e880b2c0..c82b3d69a3 100644 --- a/app/client/src/components/ads/Icon.tsx +++ b/app/client/src/components/ads/Icon.tsx @@ -21,6 +21,10 @@ import { ReactComponent as ContextMenuIcon } from "assets/icons/ads/context-menu import { ReactComponent as DuplicateIcon } from "assets/icons/ads/duplicate.svg"; import { ReactComponent as LogoutIcon } from "assets/icons/ads/logout.svg"; import { ReactComponent as ManageIcon } from "assets/icons/ads/manage.svg"; +import { ReactComponent as ArrowLeft } from "assets/icons/ads/arrow-left.svg"; +import { ReactComponent as Fork } from "assets/icons/ads/fork.svg"; +import { ReactComponent as ChevronLeft } from "assets/icons/ads/chevron_left.svg"; +import { ReactComponent as ChevronRight } from "assets/icons/ads/chevron_right.svg"; import styled from "styled-components"; import { CommonComponentProps, Classes } from "./common"; import { noop } from "lodash"; @@ -95,11 +99,15 @@ export const IconCollection = [ "duplicate", "logout", "manage", + "arrow-left", + "fork", + "chevron-left", + "chevron-right", ] as const; export type IconName = typeof IconCollection[number]; -const IconWrapper = styled.span` +export const IconWrapper = styled.span` &:focus { outline: none; } @@ -208,6 +216,18 @@ const Icon = forwardRef( case "warning": returnIcon = ; break; + case "arrow-left": + returnIcon = ; + break; + case "fork": + returnIcon = ; + break; + case "chevron-left": + returnIcon = ; + break; + case "chevron-right": + returnIcon = ; + break; default: returnIcon = null; break; diff --git a/app/client/src/components/ads/Menu.tsx b/app/client/src/components/ads/Menu.tsx index 5748732665..116dcfed15 100644 --- a/app/client/src/components/ads/Menu.tsx +++ b/app/client/src/components/ads/Menu.tsx @@ -3,6 +3,7 @@ import { CommonComponentProps } from "./common"; import styled from "styled-components"; import { Popover } from "@blueprintjs/core/lib/esm/components/popover/popover"; import { Position } from "@blueprintjs/core/lib/esm/common/position"; +import { PopperModifiers } from "@blueprintjs/core"; type MenuProps = CommonComponentProps & { children?: ReactNode[]; @@ -10,6 +11,7 @@ type MenuProps = CommonComponentProps & { position?: Position; onOpening?: (node: HTMLElement) => void; onClosing?: (node: HTMLElement) => void; + modifiers?: PopperModifiers; }; const MenuWrapper = styled.div` @@ -33,6 +35,7 @@ const Menu = (props: MenuProps) => { portalClassName={props.className} data-cy={props.cypressSelector} disabled={props.disabled} + modifiers={props.modifiers} > {props.target} diff --git a/app/client/src/components/ads/Text.tsx b/app/client/src/components/ads/Text.tsx index 969b2ad208..c2a58a9a06 100644 --- a/app/client/src/components/ads/Text.tsx +++ b/app/client/src/components/ads/Text.tsx @@ -70,7 +70,7 @@ const Text = styled.span.attrs((props: TextProps) => ({ letter-spacing: ${(props) => props.theme.typography[props.type].letterSpacing}px; color: ${(props) => - props.highlight ? props.theme.colors.text.hightlight : typeSelector(props)}; + props.highlight ? props.theme.colors.text.highlight : typeSelector(props)}; text-transform: ${(props) => (props.case ? props.case : "none")}; `; diff --git a/app/client/src/components/ads/Tooltip.tsx b/app/client/src/components/ads/Tooltip.tsx index f40b3c2a02..9b9d849af4 100644 --- a/app/client/src/components/ads/Tooltip.tsx +++ b/app/client/src/components/ads/Tooltip.tsx @@ -1,7 +1,7 @@ import React from "react"; import { CommonComponentProps } from "./common"; import styled from "styled-components"; -import { Position, Tooltip, Classes } from "@blueprintjs/core"; +import { Position, Tooltip, Classes, PopperBoundary } from "@blueprintjs/core"; import { Classes as CsClasses } from "./common"; type Variant = "dark" | "light"; @@ -12,6 +12,8 @@ type TooltipProps = CommonComponentProps & { children: JSX.Element; variant?: Variant; maxWidth?: number; + usePortal?: boolean; + boundary?: PopperBoundary; }; const TooltipWrapper = styled.div<{ variant?: Variant; maxWidth?: number }>` @@ -24,6 +26,16 @@ const TooltipWrapper = styled.div<{ variant?: Variant; maxWidth?: number }>` : props.theme.colors.tooltip.lightBg}; } div.${Classes.POPOVER_ARROW} { + path { + fill: ${(props) => + props.variant === "dark" + ? props.theme.colors.tooltip.darkBg + : props.theme.colors.tooltip.lightBg}; + stroke: ${(props) => + props.variant === "dark" + ? props.theme.colors.tooltip.darkBg + : props.theme.colors.tooltip.lightBg}; + } display: block; } .${Classes.TOOLTIP} { @@ -52,7 +64,8 @@ const TooltipComponent = (props: TooltipProps) => { {props.children} diff --git a/app/client/src/components/designSystems/appsmith/header/DeployLinkButton.tsx b/app/client/src/components/designSystems/appsmith/header/DeployLinkButton.tsx index 711a42bbd6..7fd9615df8 100644 --- a/app/client/src/components/designSystems/appsmith/header/DeployLinkButton.tsx +++ b/app/client/src/components/designSystems/appsmith/header/DeployLinkButton.tsx @@ -1,34 +1,14 @@ import React, { ReactNode, useState } from "react"; -import styled from "styled-components"; -import { Icon, Popover, PopoverPosition, Tooltip } from "@blueprintjs/core"; -import copy from "copy-to-clipboard"; -import { PopoverInteractionKind } from "@blueprintjs/core/lib/esm/components/popover/popover"; - -const IconContainer = styled.div` - cursor: pointer; - margin: 0 10px; - border: 1px solid #bcccd9; - border-radius: 50%; - min-width: 32px; - max-width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - flex-grow: 1; - svg { - transform: rotate(45deg); - } -`; +import styled, { withTheme } from "styled-components"; +import { Icon, Popover, PopoverPosition } from "@blueprintjs/core"; +import { Theme } from "constants/DefaultTheme"; const DeployLinkDialog = styled.div` display: flex; align-items: center; - background-color: #fff; padding: 10px; - width: 336px; - height: 62px; - color: #2e3d49; + background-color: ${(props) => + props.theme.colors.header.deployToolTipBackground}; flex-direction: row; `; @@ -36,17 +16,15 @@ const DeployLink = styled.a` display: flex; cursor: pointer; text-decoration: none; - padding-right: 10px; - color: #2e3d49; + color: ${(props) => props.theme.colors.header.deployToolTipText}; :hover { - text-decoration: none; - color: #2e3d49; + text-decoration: underline; + color: ${(props) => props.theme.colors.header.deployToolTipText}; } `; const DeployUrl = styled.div` flex: 1; - width: 222px; font-size: 12px; white-space: nowrap; overflow: hidden; @@ -57,65 +35,40 @@ const DeployUrl = styled.div` type Props = { trigger: ReactNode; link: string; + theme: Theme; }; -export const DeployLinkButton = (props: Props) => { +export const DeployLinkButton = withTheme((props: Props) => { const [isOpen, setIsOpen] = useState(false); - const [isCopied, setIsCopied] = useState(false); - const link = window.location.origin + props.link; const onClose = () => { setIsOpen(false); }; - const copyToClipboard = () => { - copy(link); - setIsCopied(true); - setTimeout(() => { - setIsCopied(false); - }, 3000); - }; - return ( - - - - - - - {link} - - + Current deployed version + } canEscapeKeyClose={false} onClose={onClose} isOpen={isOpen} - position={PopoverPosition.BOTTOM} + position={PopoverPosition.BOTTOM_RIGHT} >
setIsOpen(true)}>{props.trigger}
); -}; +}); export default DeployLinkButton; diff --git a/app/client/src/components/editorComponents/ApiResponseView.tsx b/app/client/src/components/editorComponents/ApiResponseView.tsx index ddfe36a462..c81f452717 100644 --- a/app/client/src/components/editorComponents/ApiResponseView.tsx +++ b/app/client/src/components/editorComponents/ApiResponseView.tsx @@ -98,7 +98,7 @@ interface ReduxStateProps { type Props = ReduxStateProps & RouteComponentProps; -const EMPTY_RESPONSE: ActionResponse = { +export const EMPTY_RESPONSE: ActionResponse = { statusCode: "", duration: "", body: {}, diff --git a/app/client/src/constants/ActionConstants.tsx b/app/client/src/constants/ActionConstants.tsx index 01dc05da7a..9596c5766b 100644 --- a/app/client/src/constants/ActionConstants.tsx +++ b/app/client/src/constants/ActionConstants.tsx @@ -1,3 +1,6 @@ +import { ErrorActionPayload } from "../sagas/ErrorSagas"; +import { ActionResponse } from "../api/ActionAPI"; + export type ExecuteActionPayloadEvent = { type: EventType; callback?: (result: ExecutionResult) => void; @@ -63,11 +66,10 @@ export interface PageAction { timeoutInMillisecond: number; } -export interface ExecuteErrorPayload { +export interface ExecuteErrorPayload extends ErrorActionPayload { actionId: string; - error: any; isPageLoad?: boolean; - show?: boolean; + data: ActionResponse; } // Group 1 = datasource (https://www.domain.com) diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index 2e1b047cd7..c3a8596c96 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -41,6 +41,15 @@ export enum Skin { DARK, } +export const hideScrollbar = css` + scrollbar-width: none; + -ms-overflow-style: none; + &::-webkit-scrollbar { + display: none; + -webkit-appearance: none; + } +`; + export const scrollbarDark = css` scrollbar-color: ${(props) => props.theme.colors.paneCard} ${(props) => props.theme.colors.paneBG}; @@ -285,6 +294,7 @@ export type Theme = { }; propertyPane: PropertyPaneTheme; headerHeight: string; + smallHeaderHeight: string; homePage: any; sidebarWidth: string; canvasPadding: string; @@ -511,7 +521,7 @@ type ColorType = { text: { normal: ShadeColor; heading: ShadeColor; - hightlight: ShadeColor; + highlight: ShadeColor; }; icon: { normal: ShadeColor; @@ -742,6 +752,19 @@ type ColorType = { floatingBtn: any; auth: any; formMessage: Record>; + header: { + separator: string; + appName: ShadeColor; + background: string; + deployToolTipBackground: string; + deployToolTipText: ShadeColor; + shareBtnHighlight: string; + shareBtn: string; + tabsHorizontalSeparator: string; + tabText: string; + activeTabBorderBottom: string; + activeTabText: string; + }; }; const auth: any = { @@ -772,6 +795,19 @@ const formMessage = { }; export const dark: ColorType = { + header: { + separator: darkShades[4], + appName: darkShades[7], + background: darkShades[2], + deployToolTipBackground: lightShades[10], + deployToolTipText: darkShades[7], + shareBtnHighlight: "#F86A2B", + shareBtn: "#fff", + tabsHorizontalSeparator: "#EFEFEF", + tabText: "#6F6D6D", + activeTabBorderBottom: "#FF6D2D", + activeTabText: "#000", + }, button: { disabledText: darkShades[6], }, @@ -820,7 +856,7 @@ export const dark: ColorType = { text: { normal: darkShades[6], heading: darkShades[7], - hightlight: darkShades[9], + highlight: darkShades[9], }, icon: { normal: darkShades[6], @@ -1058,6 +1094,19 @@ export const dark: ColorType = { }; export const light: ColorType = { + header: { + separator: "#E0DEDE", + appName: lightShades[8], + background: lightShades[0], + deployToolTipText: lightShades[8], + deployToolTipBackground: "#FFF", + shareBtnHighlight: "#F86A2B", + shareBtn: "#4B4848", + tabsHorizontalSeparator: "#EFEFEF", + tabText: "#6F6D6D", + activeTabBorderBottom: "#FF6D2D", + activeTabText: "#000", + }, button: { disabledText: lightShades[6], }, @@ -1106,7 +1155,7 @@ export const light: ColorType = { text: { normal: lightShades[8], heading: lightShades[9], - hightlight: lightShades[11], + highlight: lightShades[11], }, icon: { normal: lightShades[4], @@ -1560,6 +1609,7 @@ export const theme: Theme = { }, }, headerHeight: "48px", + smallHeaderHeight: "35px", canvasPadding: "20px 0 200px 0", sideNav: { maxWidth: 220, @@ -1670,7 +1720,6 @@ export const theme: Theme = { export const scrollbarLight = css<{ backgroundColor?: Color }>` scrollbar-color: ${(props) => props.theme.colors.paneText} - scrollbar-width: thin; &::-webkit-scrollbar { width: 4px; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index c7d41a158c..ea0c4e91b7 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -320,6 +320,7 @@ export const ReduxActionTypes: { [key: string]: string } = { FETCH_RELEASES_SUCCESS: "FETCH_RELEASES_SUCCESS", RESET_UNREAD_RELEASES_COUNT: "RESET_UNREAD_RELEASES_COUNT", SET_LOADING_ENTITIES: "SET_LOADING_ENTITIES", + RESET_CURRENT_APPLICATION: "RESET_CURRENT_APPLICATION", }; export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes]; diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index d8dd5a19d4..01964f6296 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -145,7 +145,7 @@ export const AUTOFIT_COLUMN = "Autofit column"; export const TIMEZONE = "Timezone"; export const ENABLE_TIME = "Enable Time"; -export const EDIT_APP = "Edit App"; +export const EDIT_APP = "Edit"; export const FORK_APP = "Fork App"; export const SIGN_IN = "Sign In"; diff --git a/app/client/src/constants/routes.ts b/app/client/src/constants/routes.ts index c0429d61c7..ff4a8d7d11 100644 --- a/app/client/src/constants/routes.ts +++ b/app/client/src/constants/routes.ts @@ -6,6 +6,7 @@ export const APPLICATIONS_URL = `/applications`; export const BUILDER_URL = "/applications/:applicationId/pages/:pageId/edit"; export const USER_AUTH_URL = "/user"; export const USERS_URL = "/users"; +export const VIEWER_URL_REGEX = /applications\/.*?\/pages\/.*/; export type BuilderRouteParams = { applicationId: string; diff --git a/app/client/src/mockResponses/PropertyPaneConfigResponse.tsx b/app/client/src/mockResponses/PropertyPaneConfigResponse.tsx index d757a50071..5b916cc376 100644 --- a/app/client/src/mockResponses/PropertyPaneConfigResponse.tsx +++ b/app/client/src/mockResponses/PropertyPaneConfigResponse.tsx @@ -711,6 +711,14 @@ const PropertyPaneConfigResponse: PropertyPaneConfigsResponse["data"] = { controlType: "SWITCH", isJSConvertible: true, }, + { + id: "4.2.0", + helpText: "Clears the input value after submit", + propertyName: "resetOnSubmit", + label: "Reset on submit", + controlType: "SWITCH", + isJSConvertible: true, + }, ], }, { diff --git a/app/client/src/pages/AppViewer/index.tsx b/app/client/src/pages/AppViewer/index.tsx index 5aee53c7db..76cd70b0be 100644 --- a/app/client/src/pages/AppViewer/index.tsx +++ b/app/client/src/pages/AppViewer/index.tsx @@ -9,7 +9,10 @@ import { BuilderRouteParams, getApplicationViewerPageURL, } from "constants/routes"; -import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import { + PageListPayload, + ReduxActionTypes, +} from "constants/ReduxActionConstants"; import { getIsInitialized } from "selectors/appViewSelectors"; import { executeAction } from "actions/widgetActions"; import { ExecuteActionPayload } from "constants/ActionConstants"; @@ -24,15 +27,19 @@ import { import { editorInitializer } from "utils/EditorUtils"; import * as Sentry from "@sentry/react"; import log from "loglevel"; +import { getPageList } from "selectors/editorSelectors"; const SentryRoute = Sentry.withSentryRouting(Route); -const AppViewerBody = styled.section` +const AppViewerBody = styled.section<{ hasPages: boolean }>` display: flex; flex-direction: row; align-items: stretch; justify-content: flex-start; - height: calc(100vh - ${(props) => props.theme.headerHeight}); + height: calc( + 100vh - + ${(props) => (!props.hasPages ? props.theme.smallHeaderHeight : "72px")} + ); `; export type AppViewerProps = { @@ -51,6 +58,7 @@ export type AppViewerProps = { propertyValue: any, ) => void; resetChildrenMetaProperty: (widgetId: string) => void; + pages: PageListPayload; } & RouteComponentProps; class AppViewer extends Component< @@ -85,7 +93,7 @@ class AppViewer extends Component< resetChildrenMetaProperty: this.props.resetChildrenMetaProperty, }} > - + 1}> {isInitialized && this.state.registered && ( ({ isInitialized: getIsInitialized(state), + pages: getPageList(state), }); const mapDispatchToProps = (dispatch: any) => ({ diff --git a/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx b/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx index 35daeb5ecf..d909898952 100644 --- a/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx +++ b/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx @@ -1,10 +1,9 @@ -import React, { useRef, useEffect, useState } from "react"; -import { Link, NavLink, useLocation } from "react-router-dom"; +import React from "react"; +import { Link, useLocation } from "react-router-dom"; import { Helmet } from "react-helmet"; import styled from "styled-components"; import StyledHeader from "components/designSystems/appsmith/StyledHeader"; -import AppsmithLogo from "assets/images/appsmith_logo_white.png"; -import Button from "components/editorComponents/Button"; +import AppsmithLogo from "assets/images/appsmith_logo.png"; import { EDIT_APP, FORK_APP, SIGN_IN } from "constants/messages"; import { isPermitted, @@ -17,7 +16,6 @@ import { import { APPLICATIONS_URL, AUTH_LOGIN_URL, - getApplicationViewerPageURL, SIGN_UP_URL, } from "constants/routes"; import { connect } from "react-redux"; @@ -27,27 +25,53 @@ import { getPageList } from "selectors/editorSelectors"; import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent"; import AppInviteUsersForm from "pages/organization/AppInviteUsersForm"; import { getCurrentOrgId } from "selectors/organizationSelectors"; -import { HeaderIcons } from "icons/HeaderIcons"; -import { Colors } from "constants/Colors"; + import { getCurrentUser } from "selectors/usersSelectors"; import { ANONYMOUS_USERNAME, User } from "constants/userConstants"; -import { isEllipsisActive } from "utils/helpers"; -import TooltipComponent from "components/ads/Tooltip"; import Text, { TextType } from "components/ads/Text"; import { Classes } from "components/ads/common"; +import { getTypographyByKey } from "constants/DefaultTheme"; +import { IconWrapper } from "components/ads/Icon"; +import Button, { Size } from "components/ads/Button"; +import ProfileDropdown from "pages/common/ProfileDropdown"; +import { Profile } from "pages/common/ProfileImage"; +import PageTabsContainer from "./PageTabsContainer"; const HeaderWrapper = styled(StyledHeader)<{ hasPages: boolean }>` - background: ${Colors.BALTIC_SEA}; - height: ${(props) => (props.hasPages ? "90px" : "48px")}; + box-shadow: unset; + height: unset; + padding: 0; + background-color: ${(props) => props.theme.colors.header.background}; color: white; flex-direction: column; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.05); .${Classes.TEXT} { max-width: 194px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - color: #d4d4d4; + ${(props) => getTypographyByKey(props, "h4")} + color: ${(props) => props.theme.colors.header.appName}; + } + + & .header__application-share-btn { + background-color: ${(props) => props.theme.colors.header.background}; + border-color: ${(props) => props.theme.colors.header.background}; + color: ${(props) => props.theme.colors.header.shareBtn}; + ${IconWrapper} path { + fill: ${(props) => props.theme.colors.header.shareBtn}; + } + } + + & .header__application-share-btn:hover { + color: ${(props) => props.theme.colors.header.shareBtnHighlight}; + ${IconWrapper} path { + fill: ${(props) => props.theme.colors.header.shareBtnHighlight}; + } + } + + & ${Profile} { + width: 24px; + height: 24px; } `; @@ -57,6 +81,7 @@ const HeaderRow = styled.div<{ justify: string }>` flex: 1; flex-direction: row; justify-content: ${(props) => props.justify}; + height: ${(props) => `calc(${props.theme.smallHeaderHeight})`}; `; const HeaderSection = styled.div<{ justify: string }>` @@ -67,66 +92,26 @@ const HeaderSection = styled.div<{ justify: string }>` `; const AppsmithLogoImg = styled.img` + padding-left: ${(props) => props.theme.spaces[7]}px; max-width: 110px; `; -const BackToEditorButton = styled(Button)` - max-width: 200px; - height: 32px; - margin: 5px 10px; +const Cta = styled(Button)` + ${(props) => getTypographyByKey(props, "btnLarge")} + height: 100%; `; -const ForkButton = styled(Button)` - max-width: 200px; - height: 32px; - margin: 5px 10px; +const ForkButton = styled(Cta)` svg { transform: rotate(-90deg); } `; -const ShareButton = styled(Button)` - height: 32px; - margin: 5px 10px; - color: white !important; -`; - -const PageTab = styled(NavLink)` +const HeaderRightItemContainer = styled.div` display: flex; - height: 30px; - max-width: 170px; - margin-right: 1px; - align-self: flex-end; - cursor: pointer; align-items: center; - justify-content: center; - text-decoration: none; - background-color: rgb(49, 48, 51); - padding: 0px 10px; - && span { - font-weight: 500; - font-size: 12px; - line-height: 20px; - letter-spacing: 0.04em; - color: #fff; - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - &&&:hover { - text-decoration: none; - background-color: #fff; - span { - color: #2e3d49; - } - } - &&&.is-active { - background-color: white; - span { - color: #2e3d49; - } - } + margin-right: ${(props) => props.theme.spaces[7]}px; + height: 100%; `; type AppViewerHeaderProps = { @@ -137,34 +122,17 @@ type AppViewerHeaderProps = { currentUser?: User; }; -const PageTabName: React.FunctionComponent<{ name: string }> = ({ name }) => { - const tabNameRef = useRef(null); - const [ellipsisActive, setEllipsisActive] = useState(false); - const tabNameText = {name}; - - useEffect(() => { - if (isEllipsisActive(tabNameRef?.current)) { - setEllipsisActive(true); - } - }, [tabNameRef]); - - return ellipsisActive ? ( - - {tabNameText} - - ) : ( - <>{tabNameText} - ); -}; - export const AppViewerHeader = (props: AppViewerHeaderProps) => { const { currentApplicationDetails, pages, currentOrgId, currentUser } = props; const isExampleApp = currentApplicationDetails?.appIsExample; const userPermissions = currentApplicationDetails?.userPermissions ?? []; const permissionRequired = PERMISSION_TYPE.MANAGE_APPLICATION; const canEdit = isPermitted(userPermissions, permissionRequired); - const queryParams = new URLSearchParams(useLocation().search); - const hideHeader = !!queryParams.get("embed"); + const { search } = useLocation(); + const queryParams = new URLSearchParams(search); + const isEmbed = queryParams.get("embed"); + const hideHeader = !!isEmbed; + const HtmlTitle = () => { if (!currentApplicationDetails?.name) return null; return ( @@ -174,16 +142,6 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => { ); }; if (hideHeader) return ; - // Mark default page as first page - const appPages = pages; - if (appPages.length > 1) { - appPages.forEach(function(item, i) { - if (item.isDefault) { - appPages.splice(i, 1); - appPages.unshift(item); - } - }); - } const forkAppUrl = `${window.location.origin}${SIGN_UP_URL}?appId=${currentApplicationDetails?.id}`; const loginAppUrl = `${window.location.origin}${AUTH_LOGIN_URL}?appId=${currentApplicationDetails?.id}`; @@ -192,14 +150,11 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => { if (props.url && canEdit) { CTA = ( - ); } else if (isExampleApp) { @@ -207,26 +162,15 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => { ); } else if ( currentApplicationDetails?.isPublic && currentUser?.username === ANONYMOUS_USERNAME ) { - CTA = ( - - ); + CTA = ; } return ( @@ -234,7 +178,7 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => { - + @@ -248,19 +192,11 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => { <> - } +