import React, { Component, useCallback, useContext, useEffect, useRef, useState, } from "react"; import styled, { ThemeContext } from "styled-components"; import { connect, useDispatch, useSelector } from "react-redux"; import { useLocation } from "react-router-dom"; import { AppState } from "reducers"; import { Classes as BlueprintClasses } from "@blueprintjs/core"; import { thinScrollbar, truncateTextUsingEllipsis, } from "constants/DefaultTheme"; import { getApplicationList, getApplicationSearchKeyword, getCreateApplicationError, getIsCreatingApplication, getIsDeletingApplication, getIsDuplicatingApplication, getIsFetchingApplications, getIsSavingOrgInfo, getUserApplicationsOrgs, getUserApplicationsOrgsList, } from "selectors/applicationSelectors"; import { ApplicationPayload, ReduxActionTypes, } from "constants/ReduxActionConstants"; import PageWrapper from "pages/common/PageWrapper"; import SubHeader from "pages/common/SubHeader"; import ApplicationCard from "./ApplicationCard"; import OrgInviteUsersForm from "pages/organization/OrgInviteUsersForm"; import { isPermitted, PERMISSION_TYPE } from "./permissionHelpers"; import FormDialogComponent from "components/editorComponents/form/FormDialogComponent"; import Dialog from "components/ads/DialogComponent"; // import OnboardingHelper from "components/editorComponents/Onboarding/Helper"; import { User } from "constants/userConstants"; import { getCurrentUser } from "selectors/usersSelectors"; import { CREATE_ORGANIZATION_FORM_NAME } from "constants/forms"; import { DropdownOnSelectActions, getOnSelectAction, } from "pages/common/CustomizedDropdown/dropdownHelpers"; import Button, { Size, Category } from "components/ads/Button"; import Text, { TextType } from "components/ads/Text"; import Icon, { IconName, IconSize } from "components/ads/Icon"; import MenuItem from "components/ads/MenuItem"; import { duplicateApplication, updateApplication, } from "actions/applicationActions"; import { Classes } from "components/ads/common"; import Menu from "components/ads/Menu"; import { Position } from "@blueprintjs/core/lib/esm/common/position"; import { UpdateApplicationPayload } from "api/ApplicationApi"; import PerformanceTracker, { PerformanceTransactionName, } from "utils/PerformanceTracker"; import { loadingUserOrgs } from "./ApplicationLoaders"; import { creatingApplicationMap } from "reducers/uiReducers/applicationsReducer"; import EditableText, { EditInteractionKind, SavingState, } from "components/ads/EditableText"; import { notEmptyValidator } from "components/ads/TextInput"; import { deleteOrg, saveOrg } from "actions/orgActions"; import { leaveOrganization } from "actions/userActions"; import CenteredWrapper from "../../components/designSystems/appsmith/CenteredWrapper"; import NoSearchImage from "../../assets/images/NoSearchResult.svg"; import { getNextEntityName, getRandomPaletteColor } from "utils/AppsmithUtils"; import { AppIconCollection } from "components/ads/AppIcon"; import ProductUpdatesModal from "pages/Applications/ProductUpdatesModal"; import WelcomeHelper from "components/editorComponents/Onboarding/WelcomeHelper"; import { useIntiateOnboarding } from "components/editorComponents/Onboarding/utils"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { createOrganizationSubmitHandler } from "../organization/helpers"; import ImportApplicationModal from "./ImportApplicationModal"; import ImportAppViaGitModal from "pages/Editor/gitSync/ImportAppViaGitModal"; import { createMessage, DOCUMENTATION, ORGANIZATIONS_HEADING, SEARCH_APPS, WELCOME_TOUR, NO_APPS_FOUND, } from "constants/messages"; import { ReactComponent as NoAppsFoundIcon } from "assets/svg/no-apps-icon.svg"; import { howMuchTimeBeforeText } from "utils/helpers"; import { setHeaderMeta } from "actions/themeActions"; import getFeatureFlags from "utils/featureFlags"; import { setIsImportAppViaGitModalOpen } from "actions/gitSyncActions"; import SharedUserList from "pages/common/SharedUserList"; import { getOnboardingOrganisations } from "selectors/onboardingSelectors"; import { getAppsmithConfigs } from "@appsmith/configs"; const OrgDropDown = styled.div` display: flex; padding: ${(props) => props.theme.spaces[4]}px ${(props) => props.theme.spaces[4]}px; font-size: ${(props) => props.theme.fontSizes[1]}px; justify-content: space-between; align-items: center; `; const ApplicationCardsWrapper = styled.div` display: flex; flex-wrap: wrap; gap: 20px; font-size: ${(props) => props.theme.fontSizes[4]}px; padding: 10px; `; const OrgSection = styled.div` margin-bottom: 40px; `; const PaddingWrapper = styled.div` display: flex; align-items: baseline; justify-content: center; width: ${(props) => props.theme.card.minWidth}px; @media screen and (min-width: 1500px) { .bp3-card { width: ${(props) => props.theme.card.minWidth}px; height: ${(props) => props.theme.card.minHeight}px; } } @media screen and (min-width: 1500px) and (max-width: 1512px) { width: ${(props) => props.theme.card.minWidth + props.theme.spaces[4] * 2}px; .bp3-card { width: ${(props) => props.theme.card.minWidth - 5}px; height: ${(props) => props.theme.card.minHeight - 5}px; } } @media screen and (min-width: 1478px) and (max-width: 1500px) { width: ${(props) => props.theme.card.minWidth + props.theme.spaces[4] * 2}px; .bp3-card { width: ${(props) => props.theme.card.minWidth - 8}px; height: ${(props) => props.theme.card.minHeight - 8}px; } } @media screen and (min-width: 1447px) and (max-width: 1477px) { width: ${(props) => props.theme.card.minWidth + props.theme.spaces[3] * 2}px; .bp3-card { width: ${(props) => props.theme.card.minWidth - 8}px; height: ${(props) => props.theme.card.minHeight - 8}px; } } @media screen and (min-width: 1417px) and (max-width: 1446px) { width: ${(props) => props.theme.card.minWidth + props.theme.spaces[3] * 2}px; .bp3-card { width: ${(props) => props.theme.card.minWidth - 11}px; height: ${(props) => props.theme.card.minHeight - 11}px; } } @media screen and (min-width: 1400px) and (max-width: 1417px) { width: ${(props) => props.theme.card.minWidth + props.theme.spaces[2] * 2}px; .bp3-card { width: ${(props) => props.theme.card.minWidth - 15}px; height: ${(props) => props.theme.card.minHeight - 15}px; } } @media screen and (max-width: 1400px) { width: ${(props) => props.theme.card.minWidth + props.theme.spaces[2] * 2}px; .bp3-card { width: ${(props) => props.theme.card.minWidth - 15}px; height: ${(props) => props.theme.card.minHeight - 15}px; } } `; const LeftPaneWrapper = styled.div` overflow: auto; width: ${(props) => props.theme.homePage.sidebar}px; height: 100%; display: flex; padding-left: 16px; padding-top: 16px; flex-direction: column; position: fixed; top: ${(props) => props.theme.homePage.header}px; box-shadow: 1px 0px 0px #ededed; `; const ApplicationContainer = styled.div` height: calc(100vh - ${(props) => props.theme.homePage.search.height - 40}px); overflow: auto; padding-right: ${(props) => props.theme.homePage.leftPane.rightMargin}px; padding-top: 16px; margin-left: ${(props) => props.theme.homePage.leftPane.width + props.theme.homePage.leftPane.rightMargin + props.theme.homePage.leftPane.leftPadding}px; width: calc( 100% - ${(props) => props.theme.homePage.leftPane.width + props.theme.homePage.leftPane.rightMargin + props.theme.homePage.leftPane.leftPadding}px ); scroll-behavior: smooth; `; const ItemWrapper = styled.div` padding: 9px 15px; `; const StyledIcon = styled(Icon)` margin-right: 11px; `; const OrgShareUsers = styled.div` display: flex; align-items: center; & .t--options-icon { margin-left: 8px; svg { path { fill: #090707; } } } & .t--new-button { margin-left: 8px; } & button, & a { padding: 4px 12px; } `; const NoAppsFound = styled.div` display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; & > span { margin-bottom: 24px; } `; function Item(props: { label: string; textType: TextType; icon?: IconName; isFetchingApplications: boolean; }) { return ( {props.icon && } {" "} {props.label} ); } const LeftPaneDataSection = styled.div` position: relative; height: calc(100vh - ${(props) => props.theme.homePage.header + 24}px); `; function LeftPaneSection(props: { heading: string; children?: any; isFetchingApplications: boolean; }) { return ( {/* */} {props.children} ); } const StyledAnchor = styled.a` position: relative; top: -24px; `; const WorkpsacesNavigator = styled.div` overflow: auto; height: calc(100vh - ${(props) => props.theme.homePage.header + 252}px); ${thinScrollbar}; /* padding-bottom: 160px; */ `; const LeftPaneBottomSection = styled.div` position: absolute; bottom: 0; left: 0; width: 100%; padding-bottom: 8px; background-color: #fff; & .ads-dialog-trigger { margin-top: 4px; } & .ads-dialog-trigger > div { position: initial; width: 92%; padding: 0 14px; } `; const LeftPaneVersionData = styled.div` display: flex; justify-content: space-between; color: #121826; font-size: 8px; width: 92%; margin-top: 8px; `; const textIconStyles = (props: { color: string; hover: string }) => { return ` &&&&&& { .${Classes.TEXT},.${Classes.ICON} svg path { color: ${props.color}; stroke: ${props.color}; fill: ${props.color}; } &:hover { .${Classes.TEXT},.${Classes.ICON} svg path { color: ${props.hover}; stroke: ${props.hover}; fill: ${props.hover}; } } } `; }; function OrgMenuItem({ isFetchingApplications, org, selected }: any) { const menuRef = useRef(null); useEffect(() => { if (selected) { menuRef.current?.scrollIntoView({ behavior: "smooth" }); menuRef.current?.click(); } }, [selected]); return ( ); } const submitCreateOrganizationForm = async (data: any, dispatch: any) => { const result = await createOrganizationSubmitHandler(data, dispatch); return result; }; function LeftPane() { const dispatch = useDispatch(); const fetchedUserOrgs = useSelector(getUserApplicationsOrgs); const onboardingOrgs = useSelector(getOnboardingOrganisations); const isFetchingApplications = useSelector(getIsFetchingApplications); const { appVersion } = getAppsmithConfigs(); const howMuchTimeBefore = howMuchTimeBeforeText(appVersion.releaseDate); let userOrgs; if (!isFetchingApplications) { userOrgs = fetchedUserOrgs; } else { userOrgs = loadingUserOrgs as any; } const location = useLocation(); const urlHash = location.hash.slice(1); const initiateOnboarding = useIntiateOnboarding(); return ( {!isFetchingApplications && fetchedUserOrgs && ( submitCreateOrganizationForm( { name: getNextEntityName( "Untitled organization ", fetchedUserOrgs.map((el: any) => el.organization.name), ), }, dispatch, ) } text={CREATE_ORGANIZATION_FORM_NAME} /> )} {userOrgs && userOrgs.map((org: any) => ( ))} { window.open("https://discord.gg/rBTTVJp", "_blank"); }} text={"Join our Discord"} /> { window.open("https://docs.appsmith.com/", "_blank"); }} text={createMessage(DOCUMENTATION)} /> {!!onboardingOrgs.length && ( { AnalyticsUtil.logEvent("WELCOME_TOUR_CLICK"); initiateOnboarding(); }} text={createMessage(WELCOME_TOUR)} /> )} Appsmith {appVersion.id} {howMuchTimeBefore !== "" && ( Released {howMuchTimeBefore} ago )} ); } const CreateNewLabel = styled(Text)` margin-top: 18px; `; const OrgNameElement = styled(Text)` max-width: 500px; ${truncateTextUsingEllipsis} `; const OrgNameHolder = styled(Text)` display: flex; align-items: center; `; const OrgNameWrapper = styled.div<{ disabled?: boolean }>` ${(props) => { const color = props.disabled ? props.theme.colors.applications.orgColor : props.theme.colors.applications.hover.orgColor[9]; return `${textIconStyles({ color: color, hover: color, })}`; }} .${Classes.ICON} { display: ${(props) => (!props.disabled ? "inline" : "none")}; margin-left: 8px; color: ${(props) => props.theme.colors.applications.iconColor}; } `; const OrgRename = styled(EditableText)` padding: 0 2px; `; const NoSearchResultImg = styled.img` margin: 1em; `; function ApplicationsSection(props: any) { const enableImportExport = true; const dispatch = useDispatch(); const theme = useContext(ThemeContext); const isSavingOrgInfo = useSelector(getIsSavingOrgInfo); const isFetchingApplications = useSelector(getIsFetchingApplications); const userOrgs = useSelector(getUserApplicationsOrgsList); const creatingApplicationMap = useSelector(getIsCreatingApplication); const currentUser = useSelector(getCurrentUser); const deleteApplication = (applicationId: string) => { if (applicationId && applicationId.length > 0) { dispatch({ type: ReduxActionTypes.DELETE_APPLICATION_INIT, payload: { applicationId, }, }); } }; const [warnLeavingOrganization, setWarnLeavingOrganization] = useState(false); const [warnDeleteOrg, setWarnDeleteOrg] = useState(false); const [orgToOpenMenu, setOrgToOpenMenu] = useState(null); const updateApplicationDispatch = ( id: string, data: UpdateApplicationPayload, ) => { dispatch(updateApplication(id, data)); }; const duplicateApplicationDispatch = (applicationId: string) => { dispatch(duplicateApplication(applicationId)); }; const [selectedOrgId, setSelectedOrgId] = useState(); const [ selectedOrgIdForImportApplication, setSelectedOrgIdForImportApplication, ] = useState(); const Form: any = OrgInviteUsersForm; const leaveOrg = (orgId: string) => { setWarnLeavingOrganization(false); setOrgToOpenMenu(null); dispatch(leaveOrganization(orgId)); }; const handleDeleteOrg = useCallback( (orgId: string) => { setWarnDeleteOrg(false); setOrgToOpenMenu(null); dispatch(deleteOrg(orgId)); }, [dispatch], ); const OrgNameChange = (newName: string, orgId: string) => { dispatch( saveOrg({ id: orgId as string, name: newName, }), ); }; function OrgMenuTarget(props: { orgName: string; disabled?: boolean; orgSlug: string; }) { const { disabled, orgName, orgSlug } = props; return ( {orgName} ); } const createNewApplication = (applicationName: string, orgId: string) => { const color = getRandomPaletteColor(theme.colors.appCardColors); const icon = AppIconCollection[Math.floor(Math.random() * AppIconCollection.length)]; return dispatch({ type: ReduxActionTypes.CREATE_APPLICATION_INIT, payload: { applicationName, orgId, icon, color, }, }); }; let updatedOrgs; if (!isFetchingApplications) { updatedOrgs = userOrgs; } else { updatedOrgs = loadingUserOrgs as any; } let organizationsListComponent; if ( !isFetchingApplications && props.searchKeyword && props.searchKeyword.trim().length > 0 && updatedOrgs.length === 0 ) { organizationsListComponent = ( {createMessage(NO_APPS_FOUND)} ); } else { organizationsListComponent = updatedOrgs.map( (organizationObject: any, index: number) => { const { applications, organization } = organizationObject; const hasManageOrgPermissions = isPermitted( organization.userPermissions, PERMISSION_TYPE.MANAGE_ORGANIZATION, ); return ( {(currentUser || isFetchingApplications) && OrgMenuTarget({ orgName: organization.name, orgSlug: organization.slug, })} {selectedOrgIdForImportApplication && ( setSelectedOrgIdForImportApplication("")} organizationId={selectedOrgIdForImportApplication} /> )} {hasManageOrgPermissions && ( setSelectedOrgId("")} title={`Invite Users to ${organization.name}`} >
)} {isPermitted( organization.userPermissions, PERMISSION_TYPE.INVITE_USER_TO_ORGANIZATION, ) && !isFetchingApplications && ( } /> {isPermitted( organization.userPermissions, PERMISSION_TYPE.CREATE_APPLICATION, ) && !isFetchingApplications && applications.length !== 0 && (