PromucFlow_constructor/app/client/src/pages/Editor/ToggleModeButton.tsx
Pawan Kumar 8395f5e18f
feat: property pane docking (#7361)
* add tailwindcss

* docked property pane

* uncomment a line

* make entity explorer as drawer on unpin

* remove unused imports

* add pin state in  reducer

* add menu icon in header

* fix widget sidebar

* fix widgets sidebar

* style property pane

* update property pane css

* update icons in property pane

* update property pane header styles

* update spacing

* fix few ui issues

* wip: preview mode

* wip:preview mode

* remove unused import

* comments sidebar in app and edit mode

* fix order of import

* use selected state for property pane

* update scrollbar style

* add classes to sidebar and property pane

* make widgets editor fluid

* make widgets editor fluid and refactor logic

* resize the widgets editor if explorer is pinned

* add shortcut for preview mode

* fix link for tabs in edit mode

* zoom in/zoom out for 0.75

* fix chart widget + table widget crashing

* allow zooming of canvas

* fix weird canvas draw issue + update container for handling zoom

* add actions for is panning

* allow panning with grab cursor

* reset panning + zooming when entering preview mode

* add grabbing cursor when grabbing

* only prevent default when space key is pressed

* dont allow zoom in preview mode

* remove unused imports

* fix dont allow zoom in preview mode

* fix ux of panning on space hit

* make fluid as the default app layout

* chart spec

* fix dropdown_on change spec

* fix add widget table and bind spec

* remove draggable property pane spec

* fix container spec

* fix form widget spec

* fix jest test

* fix the function typo

* remove clicking of close button for property pane in cypress tests

* remove property pane actions test

* fix drag and drop test failing

* add cypress selector id to back button in property pane

* fix toggle js spec

* fix merge conflicts from new design system

* editor header

* fix product updates styles + widget card

* remove all unused imports

* fix dynamic layout spec

* fix entity explorer tab rename test failing

* fix table spec

* fix bind tabletextpagination spec

* fix js object spec

* fix entity explorer rename issue

* fix cypress test

* fix cypress command wrong commit

* fix tab spec

* fix property pane copy tests

* add zoom header

* zoom levels

* make property pane sidebar resizable

* add multi select property pane

* fix widget search bug

* update property pane width in state on drag end

* fix viewer header

* fix editor header

* update editor header + remove zooming

* update small style

* dont allow closing of explorer when resizing

* fix jest test

* fix dropdown widget jest test

* preview test case wip

* add entity explorer pinning tests + preview mode tests

* add tooltip in layout control + add padding bottom in property pane view

* incorporate aakash feedbacks

* fix preview mode margin issue

* remove panning code

* fix cypress failing test

* uncomment jest test

* remove redundant code

* fix maincontainer test

* incorporate review feedbacks

* incorporate aakash feedbacks

* review feedbacks

* incorporate review feedbacks

* incorporate qa feedbacks

* fix dynamic layout spec

* updated test based on latest change

* dsl updated

* Updated dsl

* Updated dsl

* resize deselects widget issue.

* fix canvas height issue

* fix typo

* incorporate qa feedbacks

* incorporate qa feedbacks

* incorporate qa feedbacks

* update color for setting control for widget name

* fix onboarding styles conflicts

* Updated tests

* fix application overflow issue

* updated test method

Co-authored-by: root <root@DESKTOP-9GENCK0.localdomain>
Co-authored-by: Pawan Kumar <pawankumar@Pawans-MacBook-Pro.local>
Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com>
Co-authored-by: Apple <nandan@thinkify.io>
2021-11-23 13:31:46 +05:30

430 lines
13 KiB
TypeScript

import React, { useCallback, useEffect, useMemo, 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 Pen from "remixicon-react/PencilFillIcon";
import Eye from "remixicon-react/EyeLineIcon";
import { ReactComponent as CommentModeUnread } from "assets/icons/comments/comment-mode-unread-indicator.svg";
import { ReactComponent as CommentMode } from "assets/icons/comments/chat.svg";
import { Indices } from "constants/Layers";
import {
setCommentMode as setCommentModeAction,
fetchApplicationCommentsRequest,
showCommentsIntroCarousel,
} from "actions/commentActions";
import {
commentModeSelector,
getAppCommentThreads,
getCommentsState,
} 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 { 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 {
commentsTourStepsEditModeTypes,
commentsTourStepsPublishedModeTypes,
} from "comments/tour/commentsTourSteps";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { getCurrentApplicationId } from "../../selectors/editorSelectors";
import { getAppMode } from "../../selectors/applicationSelectors";
import { setPreviewModeAction } from "actions/editorActions";
import { previewModeSelector } from "selectors/editorSelectors";
import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
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;
z-index: ${Indices.Layer1};
`;
/**
* 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 currentBranch = useSelector(getCurrentGitBranch);
const handleLocationUpdate = async () => {
if (!currentUser) return;
const searchParams = new URL(window.location.href).searchParams;
const isCommentMode = searchParams.get("isCommentMode");
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 && !currentUser?.commentOnboardingState) {
AnalyticsUtil.logEvent("COMMENTS_ONBOARDING_MODAL_TRIGGERED");
dispatch(showCommentsIntroCarousel());
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(() => {
dispatch(fetchApplicationCommentsRequest());
}, [isCommentMode, currentBranch]);
};
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 size={20} />
</TooltipComponent>
);
}
function ViewModeReset() {
return (
<TooltipComponent
content={
<>
View Mode
<span style={{ color: "#fff", marginLeft: 20 }}>V</span>
</>
}
hoverOpenDelay={1000}
position={Position.BOTTOM}
>
<Eye size={20} />
</TooltipComponent>
);
}
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,
},
};
function ViewOrEditMode({ mode }: { mode?: APP_MODE }) {
return mode === APP_MODE.EDIT ? <EditModeReset /> : <ViewModeReset />;
}
function CommentModeBtn({
handleSetCommentModeButton,
isCommentMode,
showSelectedMode,
showUnreadIndicator,
}: {
handleSetCommentModeButton: () => void;
isCommentMode: boolean;
showUnreadIndicator: boolean;
showSelectedMode: boolean;
}) {
const CommentModeIcon = showUnreadIndicator ? CommentModeUnread : CommentMode;
const commentModeClassName = showUnreadIndicator
? `t--toggle-comment-mode-on--unread`
: `t--toggle-comment-mode-on`;
return (
<ModeButton
active={isCommentMode}
className={`t--switch-comment-mode-on ${commentModeClassName}`}
onClick={handleSetCommentModeButton}
showSelectedMode={showSelectedMode}
type="stroke"
>
<TooltipComponent
content={
<>
Comment Mode
<span style={{ color: "#fff", marginLeft: 20 }}>C</span>
</>
}
hoverOpenDelay={1000}
position={Position.BOTTOM}
>
<CommentModeIcon />
</TooltipComponent>
</ModeButton>
);
}
export const useHideComments = () => {
const [shouldHide, setShouldHide] = useState(true);
const location = useLocation();
const currentUser = useSelector(getCurrentUser);
useEffect(() => {
const pathName = window.location.pathname;
const shouldShow = matchBuilderPath(pathName) || matchViewerPath(pathName);
// Disable comment mode toggle for anonymous users
setShouldHide(
!shouldShow ||
!currentUser ||
currentUser.username === ANONYMOUS_USERNAME,
);
}, [location, currentUser]);
return shouldHide;
};
type ToggleCommentModeButtonProps = {
showSelectedMode?: boolean;
};
export const useHasUnreadCommentThread = (applicationId: string) => {
const commentsState = useSelector(getCommentsState);
return useMemo(() => {
return !!getAppCommentThreads(
commentsState.applicationCommentThreadsByRef[applicationId],
).find((tId: string) => !commentsState.commentThreadsMap[tId]?.isViewed);
}, [commentsState]);
};
function ToggleCommentModeButton({
showSelectedMode = true,
}: ToggleCommentModeButtonProps) {
const dispatch = useDispatch();
const isCommentMode = useSelector(commentModeSelector);
const isPreviewMode = useSelector(previewModeSelector);
const currentUser = useSelector(getCurrentUser);
const appId = useSelector(getCurrentApplicationId) || "";
const appMode = useSelector(getAppMode);
const hasUnreadCommentThread = useHasUnreadCommentThread(appId);
// show comments indicator only if -
// 1. user hasn't completed their comments onboarding and they are in published mode or
// 2. There is at least one unread comment thread
const showUnreadIndicator =
hasUnreadCommentThread ||
(!currentUser?.commentOnboardingState &&
appMode === APP_MODE.PUBLISHED &&
currentUser?.username !== ANONYMOUS_USERNAME);
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);
const handleSetCommentModeButton = useCallback(() => {
AnalyticsUtil.logEvent("COMMENTS_TOGGLE_MODE", {
mode: "COMMENT",
source: "CLICK",
});
setCommentModeInUrl(true);
dispatch(setPreviewModeAction(false));
proceedToNextTourStep();
}, [proceedToNextTourStep]);
// Show comment mode button only on the canvas editor and viewer
const isHideComments = useHideComments();
const onClickPreviewModeButton = useCallback(() => {
dispatch(setPreviewModeAction(true));
setCommentModeInUrl(false);
}, [dispatch, setPreviewModeAction]);
if (isHideComments) return null;
return (
<Container className="t--comment-mode-switch-toggle">
<TourTooltipWrapper {...tourToolTipProps}>
<div style={{ display: "flex" }}>
<ModeButton
active={!isCommentMode && !isPreviewMode}
className="t--switch-comment-mode-off"
onClick={() => {
AnalyticsUtil.logEvent("COMMENTS_TOGGLE_MODE", {
mode,
source: "CLICK",
});
setCommentModeInUrl(false);
dispatch(setPreviewModeAction(false));
}}
showSelectedMode={showSelectedMode}
type="fill"
>
<ViewOrEditMode mode={mode} />
</ModeButton>
<CommentModeBtn
handleSetCommentModeButton={handleSetCommentModeButton}
isCommentMode={isCommentMode || isTourStepActive} // Highlight the button during the tour
showSelectedMode={showSelectedMode}
showUnreadIndicator={showUnreadIndicator}
/>
{appMode === APP_MODE.EDIT && (
<TooltipComponent
content={
<>
Preview Mode
<span style={{ color: "#fff", marginLeft: 20 }}>P</span>
</>
}
hoverOpenDelay={1000}
position={Position.BOTTOM}
>
<ModeButton
active={isPreviewMode}
className="t--switch-preview-mode-toggle"
onClick={onClickPreviewModeButton}
showSelectedMode={showSelectedMode}
type="fill"
>
<Eye size={20} />
</ModeButton>
</TooltipComponent>
)}
</div>
</TourTooltipWrapper>
</Container>
);
}
export default ToggleCommentModeButton;