PromucFlow_constructor/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx
Rishabh Saxena a0d2e8533d
Initialise comments (#3328)
* Initial scaffolding for comments CRUD APIs

* add actions

* add assets

* state management for existing comments and creating new

* add ui components

* add overlay comments wrapper to baseWidget

* add toggle comment mode button at editor header

* trigger tests

* Disallow commenting as someone else

* Add applicationId for comments

* lint

* Add overlay blacklist to prevent component interaction while adding comments

* Comment thread style updates

* Placeholder comment context menu

* Controlled comment thread visibility for making new comments visible by default

* Update comment type description

* Reset input on save

* Resolve comment thread button ui

* fix close on esc key, dont create new comment on outside click

* Submit on enter

* add emoji picker

* Attempt at adding a websocket server in Java

* CRUD APIs for comment threads

* Add API for getting all threads in application

* Move types to a separate file

* Initial commit for real time server (RTS)

* Add script to start RTS

* Fix position property

* Use create comment thread API

* Use add comment to thread API

* Add custom cursor

* Dispatch logout init on 401 errors

* Allow CORS for real time connection

* Add more logs to RTS

* Fix construction of MongoClient

* WIP: Real time comments

* Enable comments

* Minor updates

* Read backend API base URL from environment

* Escape to reset comments mode

* Set popover position as auto and boundary as scroll parent

* Disable warning

* Added permissions for comment threads

* Add resolved API for comment threads

* Migration to set commenting permission on existing apps

* Fix updates bringing the RTS down

* Show view latest button, scroll to bottom on creating a new comment

* Cleanup comment reducer

* Move to typescript for RTS

* Add missing server.ts and tsconfig files

* Resolve / unresolve comment

* Scaffold app comments

* Minor fixes: comment on top of all widgets, add toggle button at viewer header

* Reconnect socket on creating a new app, set connected status in store

* Retry socket connection flow

* Integration tests for comments with api mocks using msw

* Fix circular depependency

* rm file

* Minor cleanup and comments

* Minor refactors: move isScrolledToBottom to common hooks, decouple prevent interactions overlay from comments wrapper

* Use policies when pushing updates in RTS

* ENV var to set if comments are enabled

* Fix: check if editor/viewer is initialised before waiting for init action

* Add tests for comments reducer

* Revert "ENV var to set if comments are enabled"

This reverts commit 988efeaa69d378d943a387e1e73510334958adc5.

* Enable comments for users with appsmith email

* lint

* fix

* Try running a socket.io server inside backend

* Update comment reducer tests

* Init mentions within comments

* Fix comment thread updates with email rooms

* Minor fixes

* Refactors / review suggestions

* lint

* increase cache limit for builds

* Comment out tests for feature that's under development

* Add Dockerfile for RTS

* Fix policies missing for first comment in threads

* Use draftJS for comments input with mentions support

* fix fixtures

* Use thread's policies when querying for threads

* Update socket.io to v4

* Add support for richer body with mentions

* Update comment body type to RawDraftContentState

* fix stale method

* Fix mentions search

* Minor cleanups

* Comment context menu and thread UI updates

* revert: Scaffold app comments

* Yarn dependencies

* Delete comment using id api added

* Init app comments

* Add test for creating thread

* Api for delete comment with id

* Test comment creation response and policies

* Copy comment links

* Fix reset editor state

* Delete valid comment testcase added

* Delete comment TC : code refactor

* Don't allow creating comments with an empty body

* Pin comments WIP[]

* Ignore dependency-reduced-pom.xml files from VCS

* Cleanup of some dev-only files, for review

* Delete comment

* Update socket.io to v4 in RTS

* Pin and resolve comment thread object added in commentThread

* Pin and resolve comment thread object added in commentThread

* Update comment thread API

* Added creationTime and updationTime in comment thread response

* Added creationTime and updationTime in comment thread response

* Added human readable id to comment threads, fallback to username for null name in user document

* Refactor

* lint

* fix test, rm duplicate selector

* comment out saga used for dev

* CommentThread viewed status, username fallback for getName=null, username field added in pin & resolve status

* lint

* trigger tests

Co-authored-by: Shrikant Sharat Kandula <shrikant@appsmith.com>
Co-authored-by: Abhijeet <abhi.nagarnaik@gmail.com>
2021-04-29 16:03:51 +05:30

238 lines
7.7 KiB
TypeScript

import React from "react";
import { Link, useLocation } from "react-router-dom";
import { Helmet } from "react-helmet";
import styled, { ThemeProvider } from "styled-components";
import StyledHeader from "components/designSystems/appsmith/StyledHeader";
import AppsmithLogo from "assets/images/appsmith_logo.png";
import {
isPermitted,
PERMISSION_TYPE,
} from "pages/Applications/permissionHelpers";
import {
ApplicationPayload,
PageListPayload,
} from "constants/ReduxActionConstants";
import { APPLICATIONS_URL, AUTH_LOGIN_URL } from "constants/routes";
import { connect } from "react-redux";
import { AppState } from "reducers";
import { getEditorURL } from "selectors/appViewSelectors";
import { getViewModePageList } from "selectors/editorSelectors";
import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent";
import AppInviteUsersForm from "pages/organization/AppInviteUsersForm";
import { getCurrentOrgId } from "selectors/organizationSelectors";
import { getCurrentUser } from "selectors/usersSelectors";
import { ANONYMOUS_USERNAME, User } from "constants/userConstants";
import Text, { TextType } from "components/ads/Text";
import { Classes } from "components/ads/common";
import { getTypographyByKey, Theme } 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";
import { getThemeDetails, ThemeMode } from "selectors/themeSelectors";
import ToggleCommentModeButton from "comments/ToggleCommentModeButton";
import getAppViewerHeaderCTA from "./getAppViewerHeaderCTA";
const HeaderWrapper = styled(StyledHeader)<{ hasPages: boolean }>`
box-shadow: unset;
height: unset;
padding: 0;
background-color: ${(props) => props.theme.colors.header.background};
color: white;
flex-direction: column;
.${Classes.TEXT} {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
${(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};
}
}
.header__application-fork-btn-wrapper {
height: 100%;
}
.header__application-fork-btn-wrapper .ads-dialog-trigger {
height: 100%;
}
& ${Profile} {
width: 24px;
height: 24px;
}
& .current-app-name {
overflow: auto;
}
`;
const HeaderRow = styled.div<{ justify: string }>`
width: 100%;
display: flex;
flex: 1;
flex-direction: row;
justify-content: ${(props) => props.justify};
height: ${(props) => `calc(${props.theme.smallHeaderHeight})`};
border-bottom: 1px solid
${(props) => props.theme.colors.header.tabsHorizontalSeparator};
`;
const HeaderSection = styled.div<{ justify: string }>`
display: flex;
flex: 1;
align-items: center;
justify-content: ${(props) => props.justify};
`;
const AppsmithLogoImg = styled.img`
padding-left: ${(props) => props.theme.spaces[7]}px;
max-width: 110px;
`;
const HeaderRightItemContainer = styled.div`
display: flex;
align-items: center;
margin-right: ${(props) => props.theme.spaces[7]}px;
height: 100%;
`;
const PrimaryLogoLink = styled(Link)`
display: flex;
align-items: center;
`;
type AppViewerHeaderProps = {
url?: string;
currentApplicationDetails?: ApplicationPayload;
pages: PageListPayload;
currentOrgId: string;
currentUser?: User;
lightTheme: Theme;
};
export function AppViewerHeader(props: AppViewerHeaderProps) {
const { currentApplicationDetails, currentOrgId, currentUser, pages } = props;
const userPermissions = currentApplicationDetails?.userPermissions ?? [];
const permissionRequired = PERMISSION_TYPE.MANAGE_APPLICATION;
const canEdit = isPermitted(userPermissions, permissionRequired);
const { search } = useLocation();
const queryParams = new URLSearchParams(search);
const isEmbed = queryParams.get("embed");
const hideHeader = !!isEmbed;
function HtmlTitle() {
if (!currentApplicationDetails?.name) return null;
return (
<Helmet>
<title>{currentApplicationDetails?.name}</title>
</Helmet>
);
}
if (hideHeader) return <HtmlTitle />;
const forkUrl = `${AUTH_LOGIN_URL}?redirectUrl=${window.location.href}/fork`;
const loginUrl = `${AUTH_LOGIN_URL}?redirectUrl=${window.location.href}`;
const CTA = getAppViewerHeaderCTA({
url: props.url,
canEdit,
currentApplicationDetails,
currentUser,
forkUrl,
loginUrl,
});
return (
<ThemeProvider theme={props.lightTheme}>
<HeaderWrapper hasPages={pages.length > 1}>
<HtmlTitle />
<HeaderRow justify={"space-between"}>
<HeaderSection justify={"flex-start"}>
<PrimaryLogoLink to={APPLICATIONS_URL}>
<AppsmithLogoImg alt="Appsmith logo" src={AppsmithLogo} />
</PrimaryLogoLink>
</HeaderSection>
<HeaderSection className="current-app-name" justify={"center"}>
{currentApplicationDetails && (
<Text type={TextType.H4}>{currentApplicationDetails.name}</Text>
)}
</HeaderSection>
<HeaderSection justify={"flex-end"}>
<ToggleCommentModeButton />
{currentApplicationDetails && (
<>
<FormDialogComponent
Form={AppInviteUsersForm}
applicationId={currentApplicationDetails.id}
canOutsideClickClose
orgId={currentOrgId}
title={currentApplicationDetails.name}
trigger={
<Button
className="t--application-share-btn header__application-share-btn"
icon={"share"}
size={Size.small}
text={"Share"}
/>
}
/>
{CTA && (
<HeaderRightItemContainer>{CTA}</HeaderRightItemContainer>
)}
</>
)}
{currentUser && currentUser.username !== ANONYMOUS_USERNAME && (
<HeaderRightItemContainer>
<ProfileDropdown
hideThemeSwitch
modifiers={{
offset: {
enabled: true,
offset: `0, ${pages.length > 1 ? 35 : 0}`,
},
}}
name={currentUser.name}
userName={currentUser?.username || ""}
/>
</HeaderRightItemContainer>
)}
</HeaderSection>
</HeaderRow>
<PageTabsContainer
currentApplicationDetails={currentApplicationDetails}
pages={pages}
/>
</HeaderWrapper>
</ThemeProvider>
);
}
const mapStateToProps = (state: AppState): AppViewerHeaderProps => ({
pages: getViewModePageList(state),
url: getEditorURL(state),
currentApplicationDetails: state.ui.applications.currentApplication,
currentOrgId: getCurrentOrgId(state),
currentUser: getCurrentUser(state),
lightTheme: getThemeDetails(state, ThemeMode.LIGHT),
});
export default connect(mapStateToProps)(AppViewerHeader);