PromucFlow_constructor/app/client/src/pages/Editor/ToggleModeButton.tsx

418 lines
12 KiB
TypeScript
Raw Normal View History

import React, { useCallback, useEffect, useState } from "react";
import styled from "styled-components";
import { useDispatch, useSelector } from "react-redux";
import TooltipComponent from "components/ads/Tooltip";
import TourTooltipWrapper from "components/ads/tour/TourTooltipWrapper";
import { ReactComponent as Pen } from "assets/icons/comments/pen.svg";
import { ReactComponent as Eye } from "assets/icons/comments/eye.svg";
import { ReactComponent as CommentModeUnread } from "assets/icons/comments/comment-mode-unread-indicator.svg";
import { ReactComponent as CommentMode } from "assets/icons/comments/chat.svg";
2021-06-11 15:01:32 +00:00
import { Indices } from "constants/Layers";
import {
setCommentMode as setCommentModeAction,
fetchApplicationCommentsRequest,
showCommentsIntroCarousel,
} from "actions/commentActions";
import {
commentModeSelector,
areCommentsEnabledForUserAndApp as areCommentsEnabledForUserAndAppSelector,
showUnreadIndicator as showUnreadIndicatorSelector,
} from "../../selectors/commentsSelectors";
import { getCurrentUser } from "selectors/usersSelectors";
import { useLocation } from "react-router";
import history from "utils/history";
import { Position } from "@blueprintjs/core/lib/esm/common/position";
import { TourType } from "entities/Tour";
import useProceedToNextTourStep, {
useIsTourStepActive,
} from "utils/hooks/useProceedToNextTourStep";
import { getCommentsIntroSeen } from "utils/storage";
import { ANONYMOUS_USERNAME, User } from "constants/userConstants";
import { AppState } from "reducers";
import { APP_MODE } from "entities/App";
import {
AUTH_LOGIN_URL,
matchBuilderPath,
matchViewerPath,
} from "constants/routes";
import { createMessage, UNREAD_MESSAGE } from "constants/messages";
2021-07-02 06:04:36 +00:00
import localStorage from "utils/localStorage";
import { getAppMode } from "selectors/applicationSelectors";
import { noop } from "lodash";
import {
commentsTourStepsEditModeTypes,
commentsTourStepsPublishedModeTypes,
} from "comments/tour/commentsTourSteps";
2021-07-02 06:04:36 +00:00
const getShowCommentsButtonToolTip = () => {
const flag = localStorage.getItem("ShowCommentsButtonToolTip");
return flag === null || !!flag;
};
const setShowCommentsButtonToolTip = (value = "") =>
localStorage.setItem("ShowCommentsButtonToolTip", value);
const ModeButton = styled.div<{
active: boolean;
showSelectedMode: boolean;
type: string;
}>`
position: relative;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
height: ${(props) => props.theme.smallHeaderHeight};
width: ${(props) => props.theme.smallHeaderHeight};
background: ${(props) =>
props.active && props.showSelectedMode
? props.theme.colors.comments.activeModeBackground
: "transparent"};
svg path {
fill: ${(props) =>
props.type !== "fill"
? "transparent"
: props.active
? props.theme.colors.comments.activeModeIcon
: props.theme.colors.comments.modeIcon};
stroke: ${(props) =>
props.type !== "stroke"
? "transparent"
: props.active
? props.theme.colors.comments.activeModeIcon
: props.theme.colors.comments.modeIcon};
}
svg rect:not(:first-child) {
fill: ${(props) =>
props.active
? props.theme.colors.comments.activeModeIcon
: props.theme.colors.comments.modeIcon};
}
svg circle {
stroke: ${(props) =>
props.active
? props.theme.colors.comments.activeModeIconCircleStroke
: props.theme.colors.comments.modeIconCircleStroke};
}
`;
const Container = styled.div`
display: flex;
flex: 1;
2021-06-11 15:01:32 +00:00
z-index: ${Indices.Layer1};
margin-left: ${(props) => props.theme.smallHeaderHeight};
`;
/**
* Sync comment mode in store with comment mode in URL
* Fetch app comments when comment mode is selected
*/
// eslint-disable-next-line
const useUpdateCommentMode = async (currentUser?: User) => {
const location = useLocation();
const dispatch = useDispatch();
const isCommentMode = useSelector(commentModeSelector);
const setCommentModeInStore = useCallback(
(updatedIsCommentMode) =>
dispatch(setCommentModeAction(updatedIsCommentMode)),
[],
);
const handleLocationUpdate = async () => {
if (!currentUser) return;
const searchParams = new URL(window.location.href).searchParams;
const isCommentMode = searchParams.get("isCommentMode");
const isCommentsIntroSeen = await getCommentsIntroSeen();
[Feature] new nav sniping mode (#5746) * added sniping mode toggle option to header * added cover to components on hover in sniping mode * fixed the transition time * using filled icon * Show dependencies in action pane * Added a wrapper to make a widget snipeable * removed older parts of sniping from Positioned Container * removed onclick action from snipeable wrapper * Showing widget name in different color * Added a mechanism to send user to sniping mode from successful API screen * created new property pane saga to bind the data * Fix datasource list width issue * Fix sidebar going out of view when the response is a table * Minor refactor * Show add widgets section on the sidebar * Stop showing autocomplete option after adding a widget * fetching pageId, appId from store * Get suggested widget from response * Fix table data not getting evaluated after adding binding * Fix property pane going below the entity explorer while navigating from query/api pane * Fix width of sidepane shifting for apis * Fix vertical margins of connections * Fix api pane suggested widget showing up for errors * Fix margins * can show select in canvas btn in sidebar * can get the action object at the end to bind the data * updated saga and action names * can bind data to table * Use themes * Use new image url for Table widget * Added conditional mapping for sniping mode binding. * updated the widget name tags and seq of calls to open property pane * pushed all sniping mode decoration to header * moved setting sniping mode logic to editor reducer * Added keyboard short cut to get out of sniping mode * updated reset sniping mechanism * removed a divider line * if there are no relationships, will not show the complete section * Connect Data will automatically show relevant tab in integrations * Update list and dropdown image urls * Remove create table button * no wrapping bind to text * minor review considerations * showing the widget name to left in sniping mode * can set data to datepicker * will not show snipe btn if there are no widgets in canvas * Changes for multiple suggested widgets * removed dependency of sniping from suggested widgets * Added analytics events for sniping mode * logic for binding data to a widget, moved to snipeable component * changed binding widget func from capture to onClick and took care of sniping from widget wrapper too. * added tests to check sniping mode for table * updated test spec * minor fix * Fix copy changes * Update test to use table widget from suggested widget list * if fails to bind will generate warning and keep user in sniping mode * in sniping mode will only show name plate if it is under focus * fixed the test case * added a comment * minor fix to capture on click event in sniping mode * updated text * Hide connections UI when there are no connections * Increase width to 90% * Show placeholder text and back button in sidepane * Show tooltip on hover * Add analyitcs events for suggested widgets and connections * Update label based on whether widgets are there or not * binding related changes * renamed the saga file containing sinping mode sagas * Changes for inspect entity * Revert "binding related changes" temporarily This reverts commit 54ae9667fecf24bc3cf9912a5356d06600b25c84. * Update suggested widgets url * Update table url * Fix chart data field not getting evaluated * a minor fix to show proper tool tip when user hovers on widget name * Show sidepane when there is output * Update locators * Use constants for messages * Update file name to ApiRightPane * Remove delay * Revert "Revert "binding related changes" temporarily" This reverts commit ee7f75e83218137250b4b9a28fcf63080c185150. * Fix width * Fix overlap Co-authored-by: Akash N <akash@codemonk.in>
2021-07-26 16:44:10 +00:00
const updatedIsCommentMode = isCommentMode === "true";
const notLoggedId = currentUser?.username === ANONYMOUS_USERNAME;
if (notLoggedId && updatedIsCommentMode) {
const currentUrl = window.location.href;
const path = `${AUTH_LOGIN_URL}?redirectUrl=${encodeURIComponent(
currentUrl,
)}`;
history.push(path);
return;
}
if (updatedIsCommentMode && !isCommentsIntroSeen) {
dispatch(showCommentsIntroCarousel());
2021-08-02 07:27:20 +00:00
setCommentModeInUrl(false);
} else {
setCommentModeInStore(updatedIsCommentMode);
}
};
// sync comment mode in store with comment mode in URL
useEffect(() => {
if (window.location.href) {
handleLocationUpdate();
}
}, [location, !!currentUser]);
// fetch applications comments when comment mode is turned on
useEffect(() => {
if (isCommentMode) {
dispatch(fetchApplicationCommentsRequest());
}
}, [isCommentMode]);
};
export const setCommentModeInUrl = (isCommentMode: boolean) => {
const currentURL = new URL(window.location.href);
const searchParams = currentURL.searchParams;
searchParams.set("isCommentMode", `${isCommentMode}`);
// remove comment link params so that they don't get retriggered
// on toggling comment mode
searchParams.delete("commentId");
searchParams.delete("commentThreadId");
history.replace({
pathname: currentURL.pathname,
search: searchParams.toString(),
hash: currentURL.hash,
});
};
function EditModeReset() {
return (
<TooltipComponent
content={
<>
Edit Mode
<span style={{ color: "#fff", marginLeft: 20 }}>V</span>
</>
}
hoverOpenDelay={1000}
position={Position.BOTTOM}
>
<Pen />
</TooltipComponent>
);
}
function ViewModeReset() {
return (
<TooltipComponent
content={
<>
View Mode
<span style={{ color: "#fff", marginLeft: 20 }}>V</span>
</>
}
hoverOpenDelay={1000}
position={Position.BOTTOM}
>
<Eye />
</TooltipComponent>
);
}
2021-07-02 06:04:36 +00:00
const tourToolTipProps = {
hasOverlay: true,
modifiers: {
offset: { enabled: true, offset: "3, 20" },
arrow: {
enabled: true,
fn: (data: any) => ({
...data,
offsets: {
...data.offsets,
arrow: {
top: -8,
left: 80,
},
},
}),
},
},
pulseStyles: {
top: 20,
left: 28,
height: 30,
width: 30,
},
showPulse: true,
activeStepConfig: {
[TourType.COMMENTS_TOUR_EDIT_MODE]:
commentsTourStepsEditModeTypes.ENTER_COMMENTS_MODE,
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]:
commentsTourStepsPublishedModeTypes.ENTER_COMMENTS_MODE,
},
2021-07-02 06:04:36 +00:00
};
function ViewOrEditMode({ mode }: { mode?: APP_MODE }) {
return mode === APP_MODE.EDIT ? <EditModeReset /> : <ViewModeReset />;
}
function CommentModeBtn({
handleSetCommentModeButton,
isCommentMode,
showSelectedMode,
2021-07-02 06:04:36 +00:00
showUnreadIndicator,
}: {
handleSetCommentModeButton: () => void;
isCommentMode: boolean;
showUnreadIndicator: boolean;
showSelectedMode: boolean;
2021-07-02 06:04:36 +00:00
}) {
const CommentModeIcon = showUnreadIndicator ? CommentModeUnread : CommentMode;
const commentModeClassName = showUnreadIndicator
? `t--toggle-comment-mode-on--unread`
: `t--toggle-comment-mode-on`;
2021-07-02 06:04:36 +00:00
return (
<ModeButton
active={isCommentMode}
className={`t--switch-comment-mode-on ${commentModeClassName}`}
onClick={handleSetCommentModeButton}
showSelectedMode={showSelectedMode}
type="stroke"
>
2021-07-02 06:04:36 +00:00
<TooltipComponent
content={
<>
Comment Mode
<span style={{ color: "#fff", marginLeft: 20 }}>C</span>
</>
}
hoverOpenDelay={1000}
position={Position.BOTTOM}
>
<CommentModeIcon />
</TooltipComponent>
</ModeButton>
);
}
const useShowCommentDiscoveryTooltip = (): [boolean, typeof noop] => {
const currentUser = useSelector(getCurrentUser);
const appMode = useSelector(getAppMode);
const initShowCommentButtonDiscoveryTooltip =
getShowCommentsButtonToolTip() &&
appMode === APP_MODE.PUBLISHED &&
currentUser?.username !== ANONYMOUS_USERNAME;
const [
showCommentButtonDiscoveryTooltip,
setShowCommentButtonDiscoveryTooltipInState,
] = useState(initShowCommentButtonDiscoveryTooltip);
useEffect(() => {
setShowCommentButtonDiscoveryTooltipInState(
initShowCommentButtonDiscoveryTooltip,
);
}, [appMode, currentUser]);
return [
showCommentButtonDiscoveryTooltip,
setShowCommentButtonDiscoveryTooltipInState,
];
};
export const useHideComments = () => {
const [shouldHide, setShouldHide] = useState(false);
const commentsEnabled = useSelector(areCommentsEnabledForUserAndAppSelector);
const location = useLocation();
useEffect(() => {
const pathName = window.location.pathname;
const shouldShow = matchBuilderPath(pathName) || matchViewerPath(pathName);
setShouldHide(!shouldShow);
}, [location]);
return !commentsEnabled || shouldHide;
};
type ToggleCommentModeButtonProps = {
showSelectedMode?: boolean;
};
function ToggleCommentModeButton({
showSelectedMode = true,
}: ToggleCommentModeButtonProps) {
const isCommentMode = useSelector(commentModeSelector);
const currentUser = useSelector(getCurrentUser);
2021-07-02 06:04:36 +00:00
const [
showCommentButtonDiscoveryTooltip,
setShowCommentButtonDiscoveryTooltipInState,
] = useShowCommentDiscoveryTooltip();
const showUnreadIndicator =
useSelector(showUnreadIndicatorSelector) ||
showCommentButtonDiscoveryTooltip;
useUpdateCommentMode(currentUser);
const activeStepConfig = {
[TourType.COMMENTS_TOUR_EDIT_MODE]:
commentsTourStepsEditModeTypes.ENTER_COMMENTS_MODE,
[TourType.COMMENTS_TOUR_PUBLISHED_MODE]:
commentsTourStepsPublishedModeTypes.ENTER_COMMENTS_MODE,
};
const proceedToNextTourStep = useProceedToNextTourStep(activeStepConfig);
const isTourStepActive = useIsTourStepActive(activeStepConfig);
const mode = useSelector((state: AppState) => state.entities.app.mode);
2021-07-02 06:04:36 +00:00
const handleSetCommentModeButton = useCallback(() => {
setCommentModeInUrl(true);
proceedToNextTourStep();
setShowCommentButtonDiscoveryTooltipInState(false);
setShowCommentsButtonToolTip();
}, [proceedToNextTourStep, setShowCommentButtonDiscoveryTooltipInState]);
// Show comment mode button only on the canvas editor and viewer
const isHideComments = useHideComments();
if (isHideComments) return null;
return (
<Container>
2021-07-02 06:04:36 +00:00
<TourTooltipWrapper {...tourToolTipProps}>
2021-06-11 15:01:32 +00:00
<div style={{ display: "flex" }}>
<ModeButton
active={!isCommentMode}
className="t--switch-comment-mode-off"
2021-06-11 15:01:32 +00:00
onClick={() => setCommentModeInUrl(false)}
showSelectedMode={showSelectedMode}
type="fill"
2021-06-11 15:01:32 +00:00
>
2021-07-02 06:04:36 +00:00
<ViewOrEditMode mode={mode} />
2021-06-11 15:01:32 +00:00
</ModeButton>
2021-07-02 06:04:36 +00:00
<TooltipComponent
content={createMessage(UNREAD_MESSAGE)}
2021-07-02 06:04:36 +00:00
isOpen={showCommentButtonDiscoveryTooltip}
>
2021-07-02 06:04:36 +00:00
<CommentModeBtn
{...{
handleSetCommentModeButton,
isCommentMode: isCommentMode || isTourStepActive, // Highlight the button during the tour
2021-07-02 06:04:36 +00:00
showUnreadIndicator,
showSelectedMode,
2021-07-02 06:04:36 +00:00
}}
/>
</TooltipComponent>
2021-06-11 15:01:32 +00:00
</div>
</TourTooltipWrapper>
</Container>
);
}
export default ToggleCommentModeButton;