diff --git a/app/client/src/AppRouter.tsx b/app/client/src/AppRouter.tsx index 21dbe6ab73..fe0e3db938 100644 --- a/app/client/src/AppRouter.tsx +++ b/app/client/src/AppRouter.tsx @@ -128,7 +128,7 @@ class AppRouter extends React.Component { path={SIGNUP_SUCCESS_URL} /> - + ({ payload, }); -export const fetchBranchesInit = () => ({ +export const fetchBranchesInit = (payload?: { pruneBranches: boolean }) => ({ type: ReduxActionTypes.FETCH_BRANCHES_INIT, + payload, }); export const fetchBranchesSuccess = (payload: any) => ({ @@ -226,3 +227,8 @@ export const gitPullSuccess = (mergeStatus: MergeStatus) => ({ export const resetPullMergeStatus = () => ({ type: ReduxActionTypes.RESET_PULL_MERGE_STATUS, }); + +export const remoteUrlInputValue = (payload?: { tempRemoteUrl?: string }) => ({ + type: ReduxActionTypes.SET_REMOTE_URL_INPUT_VALUE, + payload, +}); diff --git a/app/client/src/api/GitSyncAPI.tsx b/app/client/src/api/GitSyncAPI.tsx index 47fd971f0b..3360f8f818 100644 --- a/app/client/src/api/GitSyncAPI.tsx +++ b/app/client/src/api/GitSyncAPI.tsx @@ -74,9 +74,10 @@ class GitSyncAPI extends Api { destinationBranch, sourceBranch, }: MergeBranchPayload): AxiosPromise { - return Api.post( - `${GitSyncAPI.baseURL}/merge/${applicationId}?sourceBranch=${sourceBranch}&destinationBranch=${destinationBranch}`, - ); + return Api.post(`${GitSyncAPI.baseURL}/merge/${applicationId}`, { + sourceBranch, + destinationBranch, + }); } static getMergeStatus({ @@ -84,9 +85,10 @@ class GitSyncAPI extends Api { destinationBranch, sourceBranch, }: MergeStatusPayload) { - return Api.get( - `${GitSyncAPI.baseURL}/merge/status/${applicationId}?sourceBranch=${sourceBranch}&destinationBranch=${destinationBranch}`, - ); + return Api.post(`${GitSyncAPI.baseURL}/merge/status/${applicationId}`, { + sourceBranch, + destinationBranch, + }); } static pull({ applicationId }: { applicationId: string }) { @@ -105,8 +107,13 @@ class GitSyncAPI extends Api { return Api.post(`${GitSyncAPI.baseURL}/profile/default`, payload); } - static fetchBranches(applicationId: string) { - return Api.get(`${GitSyncAPI.baseURL}/branch/${applicationId}`); + static fetchBranches(applicationId: string, pruneBranches?: boolean) { + const queryParams = {} as { pruneBranches?: boolean }; + if (pruneBranches) queryParams.pruneBranches = true; + return Api.get( + `${GitSyncAPI.baseURL}/branch/${applicationId}`, + queryParams, + ); } static checkoutBranch(applicationId: string) { diff --git a/app/client/src/components/ads/Dropdown.tsx b/app/client/src/components/ads/Dropdown.tsx index 4c592e47fb..9f1d47a029 100644 --- a/app/client/src/components/ads/Dropdown.tsx +++ b/app/client/src/components/ads/Dropdown.tsx @@ -1,13 +1,23 @@ -import React, { useState, useEffect, useCallback, ReactElement } from "react"; +import React, { + useState, + useEffect, + useCallback, + ReactElement, + useRef, +} from "react"; import Icon, { IconName, IconSize } from "./Icon"; import { CommonComponentProps, Classes } from "./common"; -import Text, { TextType } from "./Text"; +import Text, { TextProps, TextType } from "./Text"; import { Popover, PopperBoundary, Position } from "@blueprintjs/core"; -import { getTypographyByKey } from "constants/DefaultTheme"; +import { getTypographyByKey, Theme } from "constants/DefaultTheme"; import styled from "constants/DefaultTheme"; import SearchComponent from "components/designSystems/appsmith/SearchComponent"; import { Colors } from "constants/Colors"; import Spinner from "./Spinner"; +import Tooltip from "components/ads/Tooltip"; +import { isEllipsisActive } from "utils/helpers"; +import SegmentHeader from "components/ads/ListSegmentHeader"; +import { useTheme } from "styled-components"; export type DropdownOnSelect = (value?: string, dropdownOption?: any) => void; @@ -23,6 +33,7 @@ export type DropdownOption = { iconColor?: string; onSelect?: DropdownOnSelect; data?: any; + isSectionHeader?: boolean; }; export interface DropdownSearchProps { enableSearch?: boolean; @@ -36,12 +47,12 @@ export interface RenderDropdownOptionType { optionClickHandler?: (dropdownOption: DropdownOption) => void; isSelectedNode?: boolean; extraProps?: any; - errorMsg?: string; + hasError?: boolean; optionWidth: string; } type RenderOption = ({ - errorMsg, + hasError, index, option, optionClickHandler, @@ -67,6 +78,7 @@ export type DropdownProps = CommonComponentProps & bgColor?: string; renderOption?: RenderOption; isLoading?: boolean; + hasError?: boolean; // should be displayed as error status without error message errorMsg?: string; // If errorMsg is defined, we show dropDown's error state with the message. placeholder?: string; helperText?: string; @@ -80,12 +92,13 @@ export type DropdownProps = CommonComponentProps & hideSubText?: boolean; boundary?: PopperBoundary; defaultIcon?: IconName; + truncateOption?: boolean; // enabled wrapping and adding tooltip on option item of dropdown menu }; export interface DefaultDropDownValueNodeProps { selected: DropdownOption; showLabelOnly?: boolean; isOpen?: boolean; - errorMsg?: string; + hasError?: boolean; renderNode?: RenderOption; placeholder?: string; showDropIcon?: boolean; @@ -102,6 +115,9 @@ export const DropdownContainer = styled.div<{ width: string; height?: string }>` width: ${(props) => props.width}; height: ${(props) => props.height || `38px`}; position: relative; + span.bp3-popover-target { + display: inline-block; + } `; const DropdownTriggerWrapper = styled.div<{ @@ -271,6 +287,10 @@ const OptionWrapper = styled.div<{ } } + .bp3-popover-wrapper { + width: 100%; + } + .${Classes.TEXT} { color: ${(props) => props.selected @@ -425,8 +445,35 @@ const ErrorLabel = styled.span` color: ${Colors.POMEGRANATE2}; `; +const StyledText = styled(Text)` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +function TooltipWrappedText( + props: TextProps & { + label: string; + }, +) { + const { label, ...textProps } = props; + const targetRef = useRef(null); + return ( + + + {label} + + + ); +} + function DefaultDropDownValueNode({ - errorMsg, + hasError, hideSubText, optionWidth, placeholder, @@ -443,7 +490,7 @@ function DefaultDropDownValueNode({ ? placeholder : "Please select a option."; function Label() { - return errorMsg ? ( + return hasError ? ( {LabelText} ) : ( {LabelText} @@ -456,16 +503,16 @@ function DefaultDropDownValueNode({ renderNode({ isSelectedNode: true, option: selected, - errorMsg, + hasError, optionWidth, }) ) : ( <> {selected?.icon ? ( 0 ? ( + return ( {option.label} + props.truncateOption ? ( + + ) : ( + {option.label} + ) ) : option.label && option.value ? ( {option.value} {option.label} + ) : props.truncateOption ? ( + ) : ( {option.value} )} @@ -578,11 +638,16 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) { ) : null} + ) : ( + ); })} - ) : null; + ); } export default function Dropdown(props: DropdownProps) { @@ -595,6 +660,7 @@ export default function Dropdown(props: DropdownProps) { errorMsg = "", placeholder, helperText, + hasError, } = { ...props }; const [isOpen, setIsOpen] = useState(false); const [selected, setSelected] = useState(props.selected); @@ -620,8 +686,9 @@ export default function Dropdown(props: DropdownProps) { [onSelect], ); + const errorFlag = hasError || errorMsg.length > 0; const disabled = props.disabled || isLoading; - const downIconColor = errorMsg ? Colors.POMEGRANATE2 : Colors.DARK_GRAY; + const downIconColor = errorFlag ? Colors.POMEGRANATE2 : Colors.DARK_GRAY; const onClickHandler = () => { if (!props.disabled) { @@ -662,14 +729,14 @@ export default function Dropdown(props: DropdownProps) { bgColor={props.bgColor} className={props.className} disabled={props.disabled} - hasError={!!errorMsg} + hasError={errorFlag} height={props.height || "38px"} isOpen={isOpen} onClick={() => setIsOpen(!isOpen)} selected={!!selected} > ; break; + case "info": + returnIcon = ; + break; case "invite-user": returnIcon = ; break; diff --git a/app/client/src/components/ads/ListSegmentHeader.tsx b/app/client/src/components/ads/ListSegmentHeader.tsx new file mode 100644 index 0000000000..1ded10ea67 --- /dev/null +++ b/app/client/src/components/ads/ListSegmentHeader.tsx @@ -0,0 +1,33 @@ +import React, { CSSProperties } from "react"; +import styled from "styled-components"; +import { getTypographyByKey } from "constants/DefaultTheme"; +import { Colors } from "constants/Colors"; + +const StyledSegmentHeader = styled.div` + padding: ${(props) => + `${props.theme.spaces[3]}px ${props.theme.spaces[5]}px`}; + padding-right: 0; + ${(props) => getTypographyByKey(props, "u2")} + color: ${Colors.GREY_10}; + display: flex; + align-items: center; +`; + +const StyledHr = styled.div` + flex: 1; + height: 1px; + background-color: ${Colors.GREY_10}; + margin-left: ${(props) => props.theme.spaces[3]}px; +`; + +export default function SegmentHeader(props: { + title: string; + style?: CSSProperties; +}) { + return ( + + {props.title} + + + ); +} diff --git a/app/client/src/components/ads/Statusbar.tsx b/app/client/src/components/ads/Statusbar.tsx index 80975a1324..169f67abca 100644 --- a/app/client/src/components/ads/Statusbar.tsx +++ b/app/client/src/components/ads/Statusbar.tsx @@ -71,10 +71,14 @@ type StatusbarProps = { percentage: number; active: boolean; message?: string; + showOnlyMessage?: boolean; }; export default function OnboardingStatusbar(props: StatusbarProps) { - const { active, message, percentage } = props; + const { active, message, percentage, showOnlyMessage } = props; + const displayMessage = showOnlyMessage + ? message + : `${percentage}% ${message}`; return ( - - {percentage}% {message} - + {displayMessage} ); diff --git a/app/client/src/components/ads/TextInput.tsx b/app/client/src/components/ads/TextInput.tsx index 755a27f8a1..c000240455 100644 --- a/app/client/src/components/ads/TextInput.tsx +++ b/app/client/src/components/ads/TextInput.tsx @@ -316,7 +316,7 @@ const TextInput = forwardRef( const inputValueValidation = props.validator && props.validator(inputValue); if (inputValueValidation) { - props.validator && setValidation(validation); + props.validator && setValidation(inputValueValidation); return ( inputValueValidation.isValid && props.onChange && diff --git a/app/client/src/constants/Colors.tsx b/app/client/src/constants/Colors.tsx index c839a9c74c..564bffb60b 100644 --- a/app/client/src/constants/Colors.tsx +++ b/app/client/src/constants/Colors.tsx @@ -159,5 +159,6 @@ export const Colors = { // error warning CRIMSON: "#D71010", ALTO_3: "#D6D6D6", + YELLOW_LIGHT: "#F4AF0A", }; export type Color = typeof Colors[keyof typeof Colors]; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 6abc4c1849..36725c8825 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -600,6 +600,7 @@ export const ReduxActionTypes = { WIDGET_ADD_NEW_TAB_CHILD: "WIDGET_ADD_NEW_TAB_CHILD", WIDGET_DELETE_TAB_CHILD: "WIDGET_DELETE_TAB_CHILD", GENERATE_SSH_KEY_PAIR_INIT: "GENERATE_SSH_KEY_PAIR_INIT", + SET_REMOTE_URL_INPUT_VALUE: "SET_REMOTE_URL_INPUT_VALUE", GENERATE_SSH_KEY_PAIR_SUCCESS: "GENERATE_SSH_KEY_PAIR_SUCCESS", REFACTOR_JS_ACTION_NAME: "REFACTOR_JS_ACTION_NAME", REFACTOR_JS_ACTION_NAME_SUCCESS: "REFACTOR_JS_ACTION_NAME_SUCCESS", @@ -655,7 +656,6 @@ export const ReduxActionErrorTypes = { UPDATE_GLOBAL_GIT_CONFIG_ERROR: "UPDATE_GLOBAL_GIT_CONFIG_ERROR", FETCH_GLOBAL_GIT_CONFIG_ERROR: "FETCH_GLOBAL_GIT_CONFIG_ERROR", CONNECT_TO_GIT_ERROR: "CONNECT_TO_GIT_ERROR", - GIT_SYNC_ERROR: "GIT_SYNC_ERROR", DISCONNECT_TO_GIT_ERROR: "DISCONNECT_TO_GIT_ERROR", COMMIT_TO_GIT_REPO_ERROR: "COMMIT_TO_GIT_REPO_ERROR", FETCH_FEATURE_FLAGS_ERROR: "FETCH_FEATURE_FLAGS_ERROR", diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index c303d2daed..08231b32c5 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -524,6 +524,7 @@ export const SNIPPET_EXECUTE = () => `Hit ⏎ to run`; export const APPLY_SEARCH_CATEGORY = () => `⏎ Jump`; // Git sync +export const CONNECTED_TO_GIT = () => "Connected to git"; export const GIT_DISCONNECT_POPUP_TITLE = () => `This will disconnect the git repository from this application`; @@ -537,16 +538,16 @@ export const DEPLOY = () => "Deploy"; export const MERGE = () => "Merge"; export const CONNECT_TO_GIT = () => "Connect to git repository"; export const CONNECT_TO_GIT_SUBTITLE = () => - "Checkout branches, Make commits, add deploy your application"; + "Checkout branches, make commits, add deploy your application"; export const REMOTE_URL = () => "Remote URL"; export const REMOTE_URL_INFO = () => - `Create an empty git repository and paste the remote URL`; -"Checkout branches, make commits, add deploy your application"; + `Create an empty git repository and paste the remote URL here.`; export const REMOTE_URL_VIA = () => "Remote URL via"; export const USER_PROFILE_SETTINGS_TITLE = () => "User settings"; export const AUTHOR_NAME = () => "Author name"; +export const AUTHOR_NAME_CANNOT_BE_EMPTY = () => "Author name cannot be empty"; export const AUTHOR_EMAIL = () => "Author email"; export const NAME_YOUR_NEW_BRANCH = () => "Name your new branch"; @@ -562,10 +563,8 @@ export const DEPLOY_TO_CLOUD = () => "Deploy to cloud"; export const DEPLOY_WITHOUT_GIT = () => "Deploy your application without version control"; export const DEPLOY_YOUR_APPLICATION = () => "Deploy your application"; -export const COMMIT = () => "COMMIT"; +export const COMMIT_CHANGES = () => "Commit changes"; export const COMMIT_TO = () => "Commit to"; -export const PUSH = () => "PUSH"; -export const PULL = () => "PULL"; export const COMMIT_AND_PUSH = () => "Commit & push"; export const PULL_CHANGES = () => "PULL CHANGES"; export const DEPLOY_KEY_TITLE = () => "Deployed Key"; @@ -573,8 +572,9 @@ export const DEPLOY_KEY_USAGE_GUIDE_MESSAGE = () => "Paste this key in your repository settings and give it write access."; export const COMMITTING_AND_PUSHING_CHANGES = () => "COMMITTING AND PUSHING CHANGES..."; +export const IS_MERGING = () => "MERGING CHANGES..."; -export const MERGE_CHANGES = () => "Merge Changes"; +export const MERGE_CHANGES = () => "Merge changes"; export const SELECT_BRANCH_TO_MERGE = () => "Select branch to merge"; export const CONNECT_GIT = () => "Connect Git"; export const RETRY = () => "RETRY"; @@ -598,10 +598,15 @@ export const NO_MERGE_CONFLICT = () => export const MERGE_CONFLICT_ERROR = () => "Merge conflicts found!"; export const FETCH_MERGE_STATUS_FAILURE = () => "Unable to fetch merge status"; export const GIT_UPSTREAM_CHANGES = () => - "Looks like there are pending upstream changes. We will pull the changesand push them to your repo."; + "Looks like there are pending upstream changes. We will pull the changes and push them to your repo."; export const GIT_CONFLICTING_INFO = () => "Please resolve the conflicts manually on your repository."; +export const CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES = () => + "You have uncommitted changes. Please commit before pulling the remote changes"; +export const CANNOT_MERGE_DUE_TO_UNCOMMITTED_CHANGES = () => + "Your current branch has uncommitted changes. Please commit before proceeding to merge"; +export const NOT_OPTIONS = () => "Not Options!"; export const OPEN_REPO = () => "OPEN REPO"; export const CONNECTING_REPO = () => "CONNECTING TO GIT REPO"; export const ERROR_CONNECTING = () => "Error while connecting"; diff --git a/app/client/src/constants/typography.ts b/app/client/src/constants/typography.ts index bf7a1a0b1d..7e49f28c68 100644 --- a/app/client/src/constants/typography.ts +++ b/app/client/src/constants/typography.ts @@ -135,6 +135,12 @@ export const typography = { fontWeight: "bold", fontSize: 13, }, + u2: { + fontSize: 10, + fontStyle: "normal", + fontWeight: 600, + lineHeight: 12, + }, }; export type TypographyKeys = keyof typeof typography; diff --git a/app/client/src/entities/FeatureFlag.ts b/app/client/src/entities/FeatureFlag.ts index 83cc5fc5f8..84c8c0c7da 100644 --- a/app/client/src/entities/FeatureFlag.ts +++ b/app/client/src/entities/FeatureFlag.ts @@ -4,6 +4,7 @@ type FeatureFlag = { SNIPPET: boolean; GIT: boolean; ADMIN_SETTINGS: boolean; + GIT_IMPORT: boolean; }; export default FeatureFlag; diff --git a/app/client/src/entities/GitSync.ts b/app/client/src/entities/GitSync.ts index 037b577766..8815ba864d 100644 --- a/app/client/src/entities/GitSync.ts +++ b/app/client/src/entities/GitSync.ts @@ -17,4 +17,6 @@ export type Branch = { export type MergeStatus = { isMergeAble: boolean; conflictingFiles: Array; + status?: string; + message?: string; }; diff --git a/app/client/src/pages/Applications/ApplicationCard.tsx b/app/client/src/pages/Applications/ApplicationCard.tsx index 4afbddcc68..5243d8a93f 100644 --- a/app/client/src/pages/Applications/ApplicationCard.tsx +++ b/app/client/src/pages/Applications/ApplicationCard.tsx @@ -54,6 +54,7 @@ import { Toaster } from "components/ads/Toast"; import { Variant } from "components/ads/common"; import { getExportAppAPIRoute } from "constants/ApiConstants"; import { Colors } from "constants/Colors"; +import { CONNECTED_TO_GIT, createMessage } from "constants/messages"; type NameWrapperProps = { hasReadPermission: boolean; @@ -363,6 +364,38 @@ const MenuItemWrapper = styled(MenuItem)` } `; +const StyledGitConnectedBadge = styled.div` + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: -12px; + right: -12px; + box-shadow: 0px 2px 16px rgba(0, 0, 0, 0.07); + background: ${Colors.WHITE}; +`; + +function GitConnectedBadge() { + return ( + + + + + + ); +} + +const Container = styled.div` + position: relative; + overflow: visible; +`; + export function ApplicationCard(props: ApplicationCardProps) { const isFetchingApplications = useSelector(getIsFetchingApplications); const theme = useContext(ThemeContext); @@ -385,6 +418,7 @@ export function ApplicationCard(props: ApplicationCardProps) { const appNameWrapperRef = useRef(null); const applicationId = props.application?.id; + const showGitBadge = props.application?.gitApplicationMetadata; useEffect(() => { let colorCode; @@ -670,82 +704,88 @@ export function ApplicationCard(props: ApplicationCardProps) { }; return ( - { - !isFetchingApplications && setShowOverlay(true); - }} - onMouseLeave={() => { - // If the menu is not open, then setOverlay false - // Set overlay false on outside click. - !isMenuOpen && setShowOverlay(false); - }} - showOverlay={showOverlay} - > - + { + !isFetchingApplications && setShowOverlay(true); + }} + onMouseLeave={() => { + // If the menu is not open, then setOverlay false + // Set overlay false on outside click. + !isMenuOpen && setShowOverlay(false); + }} + showOverlay={showOverlay} > - - - {isEllipsisActive(appNameWrapperRef?.current) ? ( - - {appNameText} - - ) : ( - appNameText + + + {isEllipsisActive(appNameWrapperRef?.current) ? ( + + {appNameText} + + ) : ( + appNameText + )} + + {showOverlay && ( +
+
+ + + {hasEditPermission && !isMenuOpen && ( + + )} + {!isMenuOpen && ( +
)} - - {showOverlay && ( -
-
- - - {hasEditPermission && !isMenuOpen && ( - - )} - {!isMenuOpen && ( -
- )} - - - {editedByText()} - {!!moreActionItems.length && ContextMenu} - - + + + {editedByText()} + {!!moreActionItems.length && ContextMenu} + + + {showGitBadge && } + ); } diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx index 548131972c..b4c9c654a6 100644 --- a/app/client/src/pages/Applications/index.tsx +++ b/app/client/src/pages/Applications/index.tsx @@ -822,7 +822,7 @@ function ApplicationsSection(props: any) { text="Import Application" /> )} - {getFeatureFlags().GIT && ( + {getFeatureFlags().GIT_IMPORT && ( {organizationsListComponent} - {getFeatureFlags().GIT && } + {getFeatureFlags().GIT_IMPORT && } ); } diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx index 6d3f16d5dc..b153288a44 100644 --- a/app/client/src/pages/Editor/Canvas.tsx +++ b/app/client/src/pages/Editor/Canvas.tsx @@ -16,6 +16,8 @@ import { useDispatch } from "react-redux"; import { initPageLevelSocketConnection } from "actions/websocketActions"; import { collabShareUserPointerEvent } from "actions/appCollabActions"; import { getIsPageLevelSocketConnected } from "../../selectors/websocketSelectors"; +import { getCurrentGitBranch } from "selectors/gitSyncSelectors"; +import { getPageLevelSocketRoomId } from "sagas/WebsocketSagas/utils"; interface CanvasProps { dsl: DSLWidget; @@ -31,6 +33,7 @@ const getPointerData = ( e: any, pageId: string, isWebsocketConnected: boolean, + currentGitBranch?: string, ) => { if (store.getState().ui.appCollab.editors.length < 2 || !isWebsocketConnected) return; @@ -41,7 +44,7 @@ const getPointerData = ( const y = e.clientY - rect.top; return { data: { x, y }, - pageId, + pageId: getPageLevelSocketRoomId(pageId, currentGitBranch), }; }; @@ -63,6 +66,7 @@ const Canvas = memo((props: CanvasProps) => { const { pageId } = props; const shareMousePointer = useShareMousePointerEvent(); const isWebsocketConnected = useSelector(getIsPageLevelSocketConnected); + const currentGitBranch = useSelector(getCurrentGitBranch); const isMultiplayerEnabledForUser = useSelector( isMultiplayerEnabledForUserSelector, ); @@ -81,7 +85,12 @@ const Canvas = memo((props: CanvasProps) => { id="art-board" onMouseMove={(e) => { if (!isMultiplayerEnabledForUser) return; - const data = getPointerData(e, pageId, isWebsocketConnected); + const data = getPointerData( + e, + pageId, + isWebsocketConnected, + currentGitBranch, + ); !!data && delayedShareMousePointer(data); }} width={props.dsl.rightColumn} diff --git a/app/client/src/pages/Editor/GlobalHotKeys.tsx b/app/client/src/pages/Editor/GlobalHotKeys.tsx index 4a00abf304..e0c598f57a 100644 --- a/app/client/src/pages/Editor/GlobalHotKeys.tsx +++ b/app/client/src/pages/Editor/GlobalHotKeys.tsx @@ -46,6 +46,8 @@ import { setPreviewModeAction } from "actions/editorActions"; import { previewModeSelector } from "selectors/editorSelectors"; import { getExplorerPinned } from "selectors/explorerSelector"; import { setExplorerPinnedAction } from "actions/explorerActions"; +import { setIsGitSyncModalOpen } from "actions/gitSyncActions"; +import { GitSyncModalTab } from "entities/GitSync"; type Props = { copySelectedWidget: () => void; @@ -73,6 +75,7 @@ type Props = { setPreviewModeAction: (shouldSet: boolean) => void; isExplorerPinned: boolean; setExplorerPinnedAction: (shouldPinned: boolean) => void; + showCommitModal: () => void; }; @HotkeysTarget @@ -369,6 +372,14 @@ class GlobalHotKeys extends React.Component { this.props.setExplorerPinnedAction(!this.props.isExplorerPinned); }} /> + { + this.props.showCommitModal(); + }} + /> ); } @@ -410,6 +421,10 @@ const mapDispatchToProps = (dispatch: any) => { dispatch(setPreviewModeAction(shouldSet)), setExplorerPinnedAction: (shouldSet: boolean) => dispatch(setExplorerPinnedAction(shouldSet)), + showCommitModal: () => + dispatch( + setIsGitSyncModalOpen({ isOpen: true, tab: GitSyncModalTab.DEPLOY }), + ), }; }; diff --git a/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.tsx b/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.tsx index 034e5a10be..a1ea38c9bc 100644 --- a/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.tsx +++ b/app/client/src/pages/Editor/gitSync/QuickGitActions/BranchButton.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import styled from "styled-components"; import { useDispatch, useSelector } from "react-redux"; @@ -11,6 +11,10 @@ import { getCurrentAppGitMetaData } from "selectors/applicationSelectors"; import BranchList from "../components/BranchList"; import { fetchBranchesInit } from "actions/gitSyncActions"; import Icon, { IconSize } from "components/ads/Icon"; +import Tooltip from "components/ads/Tooltip"; +import { Position } from "@blueprintjs/core"; +import { isEllipsisActive } from "utils/helpers"; +import { getGitStatus } from "selectors/gitSyncSelectors"; const ButtonContainer = styled.div` display: flex; @@ -42,6 +46,8 @@ function BranchButton() { const [isOpen, setIsOpen] = useState(false); const dispatch = useDispatch(); const fetchBranches = () => dispatch(fetchBranchesInit()); + const labelTarget = useRef(null); + const status = useSelector(getGitStatus); useEffect(() => { fetchBranches(); @@ -58,12 +64,23 @@ function BranchButton() { }} placement="top-start" > - -
- -
-
{currentBranch}
-
+ + +
+ +
+
+ {currentBranch} + {!status?.isClean && "*"} +
+
+
); } diff --git a/app/client/src/pages/Editor/gitSync/QuickGitActions/index.tsx b/app/client/src/pages/Editor/gitSync/QuickGitActions/index.tsx index dbdf0a5176..6fdc6a23c1 100644 --- a/app/client/src/pages/Editor/gitSync/QuickGitActions/index.tsx +++ b/app/client/src/pages/Editor/gitSync/QuickGitActions/index.tsx @@ -3,16 +3,15 @@ import styled from "styled-components"; import BranchButton from "./BranchButton"; -import { ReactComponent as UpArrow } from "assets/icons/ads/up-arrow.svg"; import { ReactComponent as DownArrow } from "assets/icons/ads/down-arrow.svg"; import { ReactComponent as Plus } from "assets/icons/ads/plus.svg"; import { ReactComponent as GitBranch } from "assets/icons/ads/git-branch.svg"; import { - COMMIT, - PUSH, - PULL, + COMMIT_CHANGES, + PULL_CHANGES, MERGE, + CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES, CONNECT_GIT, CONFLICTS_FOUND, NO_COMMITS_TO_PULL, @@ -22,7 +21,6 @@ import { DURING_ONBOARDING_TOUR, createMessage, } from "constants/messages"; -import { noop } from "lodash"; import Tooltip from "components/ads/Tooltip"; import { Colors } from "constants/Colors"; @@ -39,6 +37,7 @@ import { getPullInProgress, getIsFetchingGitStatus, getPullFailed, + getCountOfChangesToCommit, } from "selectors/gitSyncSelectors"; import SpinnerLoader from "pages/common/SpinnerLoader"; import { inOnboarding } from "sagas/OnboardingSagas"; @@ -55,7 +54,7 @@ type QuickActionButtonProps = { const QuickActionButtonContainer = styled.div<{ disabled?: boolean }>` padding: ${(props) => props.theme.spaces[1]}px ${(props) => props.theme.spaces[2]}px; - margin-left: ${(props) => props.theme.spaces[2]}px; + margin: 0 ${(props) => props.theme.spaces[2]}px; cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; &:hover { background-color: ${(props) => @@ -65,15 +64,15 @@ const QuickActionButtonContainer = styled.div<{ disabled?: boolean }>` overflow: visible; .count { position: absolute; - width: 18px; - height: 18px; + width: 20px; + height: 20px; display: flex; justify-content: center; align-items: center; color: ${Colors.WHITE}; background-color: ${Colors.BLACK}; - top: -3px; - left: 15px; + top: -8px; + left: 18px; border-radius: 50%; ${(props) => getTypographyByKey(props, "p3")}; z-index: 1; @@ -89,7 +88,7 @@ const SpinnerContainer = styled.div` display: flex; align-items: center; justify-content: center; - width: 24px; + width: 29px; height: 26px; `; @@ -120,13 +119,16 @@ function QuickActionButton({ } const getPullBtnStatus = (gitStatus: any, pullFailed: boolean) => { - const { behindCount } = gitStatus || {}; + const { behindCount, isClean } = gitStatus || {}; let message = createMessage(NO_COMMITS_TO_PULL); - const disabled = behindCount === 0; - if (pullFailed) { + let disabled = behindCount === 0; + if (!isClean) { + disabled = true; + message = createMessage(CANNOT_PULL_WITH_LOCAL_UNCOMMITTED_CHANGES); + } else if (pullFailed) { message = createMessage(CONFLICTS_FOUND); } else if (behindCount > 0) { - message = createMessage(PULL); + message = createMessage(PULL_CHANGES); } return { @@ -136,34 +138,33 @@ const getPullBtnStatus = (gitStatus: any, pullFailed: boolean) => { }; const getQuickActionButtons = ({ + changesToCommit, commit, gitStatus, + isFetchingGitStatus, merge, pull, pullDisabled, pullTooltipMessage, - push, showPullLoadingState, }: { + changesToCommit: number; commit: () => void; - push: () => void; pull: () => void; merge: () => void; gitStatus: any; + isFetchingGitStatus: boolean; pullDisabled: boolean; pullTooltipMessage: string; showPullLoadingState: boolean; }) => { return [ { + count: changesToCommit, icon: , + loading: isFetchingGitStatus, onClick: commit, - tooltipText: createMessage(COMMIT), - }, - { - icon: , - onClick: push, - tooltipText: createMessage(PUSH), + tooltipText: createMessage(COMMIT_CHANGES), }, { count: gitStatus?.behindCount, @@ -189,6 +190,7 @@ const Container = styled.div` `; const StyledIcon = styled(GitCommitLine)` + cursor: default; & path { fill: ${Colors.DARK_GRAY}; } @@ -266,6 +268,7 @@ export default function QuickGitActions() { const isPullInProgress = useSelector(getPullInProgress); const isFetchingGitStatus = useSelector(getIsFetchingGitStatus); const showPullLoadingState = isPullInProgress || isFetchingGitStatus; + const changesToCommit = useSelector(getCountOfChangesToCommit); const quickActionButtons = getQuickActionButtons({ commit: () => { @@ -276,7 +279,6 @@ export default function QuickGitActions() { }), ); }, - push: noop, pull: () => dispatch(gitPullInit({ triggeredFromBottomBar: true })), merge: () => { dispatch( @@ -287,9 +289,11 @@ export default function QuickGitActions() { ); }, gitStatus, + isFetchingGitStatus, pullDisabled, pullTooltipMessage, showPullLoadingState, + changesToCommit, }); return getFeatureFlags().GIT && isGitConnected ? ( diff --git a/app/client/src/pages/Editor/gitSync/Tabs/Deploy.tsx b/app/client/src/pages/Editor/gitSync/Tabs/Deploy.tsx index 02c9c66a67..dfe55b546c 100644 --- a/app/client/src/pages/Editor/gitSync/Tabs/Deploy.tsx +++ b/app/client/src/pages/Editor/gitSync/Tabs/Deploy.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Title } from "../components/StyledComponents"; import { DEPLOY_YOUR_APPLICATION, @@ -9,14 +9,12 @@ import { FETCH_GIT_STATUS, GIT_NO_UPDATED_TOOLTIP, GIT_UPSTREAM_CHANGES, - LEARN_MORE, PULL_CHANGES, - GIT_CONFLICTING_INFO, - OPEN_REPO, + READ_DOCUMENTATION, } from "constants/messages"; import styled, { useTheme } from "styled-components"; import TextInput from "components/ads/TextInput"; -import Button, { Category, Size } from "components/ads/Button"; +import Button, { Size } from "components/ads/Button"; import { LabelContainer } from "components/ads/Checkbox"; import { @@ -24,8 +22,8 @@ import { getIsFetchingGitStatus, getIsCommittingInProgress, getIsPullingProgress, - getGitError, getPullFailed, + getGitCommitAndPushError, } from "selectors/gitSyncSelectors"; import { useDispatch, useSelector } from "react-redux"; @@ -43,15 +41,19 @@ import { import { getIsCommitSuccessful } from "selectors/gitSyncSelectors"; import StatusLoader from "../components/StatusLoader"; import { clearCommitSuccessfulState } from "../../../../actions/gitSyncActions"; -import Statusbar from "pages/Editor/gitSync/components/Statusbar"; -import GitSyncError from "../components/GitSyncError"; +import Statusbar, { + StatusbarWrapper, +} from "pages/Editor/gitSync/components/Statusbar"; import GitChanged from "../components/GitChanged"; import Tooltip from "components/ads/Tooltip"; import Text, { TextType } from "components/ads/Text"; import { DOCS_BASE_URL } from "constants/ThirdPartyConstants"; -import log from "loglevel"; import InfoWrapper from "../components/InfoWrapper"; import Link from "../components/Link"; +import ConflictInfo from "../components/ConflictInfo"; +import Icon, { IconSize } from "components/ads/Icon"; + +import { isMac } from "utils/helpers"; const Section = styled.div` margin-bottom: ${(props) => props.theme.spaces[11]}px; @@ -65,8 +67,13 @@ const Row = styled.div` const SectionTitle = styled.div` ${(props) => getTypographyByKey(props, "p1")}; color: ${Colors.CHARCOAL}; + display: inline-flex; & .branch { color: ${Colors.CRUSTA}; + width: 240px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } `; @@ -80,29 +87,35 @@ const Container = styled.div` } `; -const StatusbarWrapper = styled.div` - width: 252px; - height: 38px; -`; - -const OpenRepoButton = styled(Button)` - margin-right: ${(props) => props.theme.spaces[3]}px; -`; - const INITIAL_COMMIT = "Initial Commit"; const NO_CHANGES_TO_COMMIT = "No changes to commit"; +function SubmitWrapper(props: { + children: React.ReactNode; + onSubmit: () => void; +}) { + const onKeyDown = (e: React.KeyboardEvent) => { + const triggerSubmit = isMac() + ? e.metaKey && e.key === "Enter" + : e.ctrlKey && e.key === "Enter"; + if (triggerSubmit) props.onSubmit(); + }; + + return
{props.children}
; +} + function Deploy() { const [commitMessage, setCommitMessage] = useState(INITIAL_COMMIT); const isCommittingInProgress = useSelector(getIsCommittingInProgress); const gitMetaData = useSelector(getCurrentAppGitMetaData); const gitStatus = useSelector(getGitStatus); const isFetchingGitStatus = useSelector(getIsFetchingGitStatus); - const isPulingProgress = useSelector(getIsPullingProgress); + const isPullingProgress = useSelector(getIsPullingProgress); const isCommitAndPushSuccessful = useSelector(getIsCommitSuccessful); const hasChangesToCommit = !gitStatus?.isClean; - const gitError = useSelector(getGitError); + const gitError = useSelector(getGitCommitAndPushError); const pullFailed = useSelector(getPullFailed); + const commitInputRef = useRef(null); const currentBranch = gitMetaData?.branchName; const dispatch = useDispatch(); @@ -134,6 +147,7 @@ function Deploy() { }, []); const commitButtonDisabled = !hasChangesToCommit || !commitMessage; const commitButtonLoading = isCommittingInProgress; + const commitInputDisabled = !hasChangesToCommit || isCommittingInProgress; const commitRequired = gitStatus?.modifiedPages || gitStatus?.modifiedQueries; const isConflicting = !isFetchingGitStatus && pullFailed; @@ -157,8 +171,12 @@ function Deploy() { const theme = useTheme() as Theme; - log.log(gitStatus); - log.log(gitError); + useEffect(() => { + if (!commitInputDisabled && commitInputRef.current) { + commitInputRef.current.focus(); + } + }, [commitInputDisabled]); + return ( {createMessage(DEPLOY_YOUR_APPLICATION)} @@ -167,72 +185,59 @@ function Deploy() { {createMessage(COMMIT_TO)} -  {currentBranch} +
 {currentBranch}
- + { + if (!commitButtonDisabled) handleCommit(true); + }} + > + + {isFetchingGitStatus && ( )} {pullRequired && !isConflicting && ( - - {createMessage(GIT_UPSTREAM_CHANGES)} - - + +
+ + {createMessage(GIT_UPSTREAM_CHANGES)} + + +
)} {pullRequired && !isConflicting && (