* 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>
238 lines
7.7 KiB
TypeScript
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);
|