feat: callouts for wip features (#8080)
This commit is contained in:
parent
bacf29848b
commit
a69c8b7484
|
|
@ -16,7 +16,7 @@ describe("Unique react keys", function() {
|
||||||
cy.dragAndDropToCanvas("chartwidget", { x: 200, y: 200 });
|
cy.dragAndDropToCanvas("chartwidget", { x: 200, y: 200 });
|
||||||
|
|
||||||
cy.dragAndDropToCanvas("dropdownwidget", { x: 200, y: 600 });
|
cy.dragAndDropToCanvas("dropdownwidget", { x: 200, y: 600 });
|
||||||
cy.dragAndDropToCanvas("dropdownwidget", { x: 200, y: 800 });
|
cy.dragAndDropToCanvas("dropdownwidget", { x: 200, y: 700 });
|
||||||
|
|
||||||
cy.openPropertyPane("chartwidget");
|
cy.openPropertyPane("chartwidget");
|
||||||
cy.deleteWidget(widgetsPage.chartWidget);
|
cy.deleteWidget(widgetsPage.chartWidget);
|
||||||
|
|
|
||||||
|
|
@ -60,3 +60,8 @@ export const collabUnsetEditorsPointersData = (payload: any) => ({
|
||||||
export const collabResetEditorsPointersData = () => ({
|
export const collabResetEditorsPointersData = () => ({
|
||||||
type: ReduxActionTypes.APP_COLLAB_RESET_EDITORS_POINTER_DATA,
|
type: ReduxActionTypes.APP_COLLAB_RESET_EDITORS_POINTER_DATA,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const collabConcurrentPageEditorsData = (payload: any) => ({
|
||||||
|
type: ReduxActionTypes.APP_COLLAB_SET_CONCURRENT_PAGE_EDITORS,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
|
||||||
82
app/client/src/comments/ConcurrentPageEditorToast.tsx
Normal file
82
app/client/src/comments/ConcurrentPageEditorToast.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { ToastComponent } from "components/ads/Toast";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { isConcurrentPageEditorToastVisible } from "selectors/appCollabSelectors";
|
||||||
|
import {
|
||||||
|
hideConcurrentEditorWarningToast,
|
||||||
|
getIsConcurrentEditorWarningToastHidden,
|
||||||
|
} from "utils/storage";
|
||||||
|
import { Layers } from "constants/Layers";
|
||||||
|
import { createGlobalStyle } from "styled-components";
|
||||||
|
|
||||||
|
const Container = styled.div<{ visible?: boolean }>`
|
||||||
|
position: fixed;
|
||||||
|
top: 50px;
|
||||||
|
transition: right 0.3s linear;
|
||||||
|
right: ${(props) =>
|
||||||
|
props.visible ? "1em" : "-500px"}; /* to move away from the viewport */
|
||||||
|
|
||||||
|
& {
|
||||||
|
.concurrent-editing-warning-text {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z-index: ${Layers.concurrentEditorWarning};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ActionElement = styled.span`
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: right;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// move existing toast below to make space for the warning toast
|
||||||
|
const ToastStyle = createGlobalStyle`
|
||||||
|
.Toastify__toast-container--top-right {
|
||||||
|
top: 10.5em !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const getMessage = () => {
|
||||||
|
const msg = `Someone else is also editing this page. Your changes may get overwritten. Realtime Editing is coming soon.`;
|
||||||
|
return msg;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ConcurrentPageEditorToast() {
|
||||||
|
const [isForceHidden, setIsForceHidden] = useState(true);
|
||||||
|
const isVisible = useSelector(isConcurrentPageEditorToastVisible);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const flag = await getIsConcurrentEditorWarningToastHidden();
|
||||||
|
setIsForceHidden(!!flag);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hidePermanently = () => {
|
||||||
|
hideConcurrentEditorWarningToast(); // save in persistent storage
|
||||||
|
setIsForceHidden(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showToast = isVisible && !isForceHidden;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container visible={showToast}>
|
||||||
|
{showToast && (
|
||||||
|
<ToastComponent
|
||||||
|
actionElement={
|
||||||
|
<ActionElement onClick={hidePermanently}>Dismiss</ActionElement>
|
||||||
|
}
|
||||||
|
contentClassName="concurrent-editing-warning-text "
|
||||||
|
hideActionElementSpace
|
||||||
|
text={getMessage()}
|
||||||
|
width={"327px"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showToast && <ToastStyle />}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import DebugButton from "components/editorComponents/Debugger/DebugCTA";
|
||||||
|
|
||||||
type ToastProps = ToastOptions &
|
type ToastProps = ToastOptions &
|
||||||
CommonComponentProps & {
|
CommonComponentProps & {
|
||||||
|
contentClassName?: string;
|
||||||
text: string;
|
text: string;
|
||||||
actionElement?: JSX.Element;
|
actionElement?: JSX.Element;
|
||||||
variant?: Variant;
|
variant?: Variant;
|
||||||
|
|
@ -20,6 +21,8 @@ type ToastProps = ToastOptions &
|
||||||
dispatchableAction?: { type: ReduxActionType; payload: any };
|
dispatchableAction?: { type: ReduxActionType; payload: any };
|
||||||
showDebugButton?: boolean;
|
showDebugButton?: boolean;
|
||||||
hideProgressBar?: boolean;
|
hideProgressBar?: boolean;
|
||||||
|
hideActionElementSpace?: boolean;
|
||||||
|
width?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const WrappedToastContainer = styled.div`
|
const WrappedToastContainer = styled.div`
|
||||||
|
|
@ -53,8 +56,9 @@ const ToastBody = styled.div<{
|
||||||
variant?: Variant;
|
variant?: Variant;
|
||||||
isUndo?: boolean;
|
isUndo?: boolean;
|
||||||
dispatchableAction?: { type: ReduxActionType; payload: any };
|
dispatchableAction?: { type: ReduxActionType; payload: any };
|
||||||
|
width?: string;
|
||||||
}>`
|
}>`
|
||||||
width: 264px;
|
width: ${(props) => props.width || "264px"};
|
||||||
background: ${(props) => props.theme.colors.toast.bg};
|
background: ${(props) => props.theme.colors.toast.bg};
|
||||||
padding: ${(props) => props.theme.spaces[4]}px
|
padding: ${(props) => props.theme.spaces[4]}px
|
||||||
${(props) => props.theme.spaces[5]}px;
|
${(props) => props.theme.spaces[5]}px;
|
||||||
|
|
@ -115,6 +119,7 @@ const FlexContainer = styled.div`
|
||||||
|
|
||||||
const ToastTextWrapper = styled.div`
|
const ToastTextWrapper = styled.div`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledDebugButton = styled(DebugButton)`
|
const StyledDebugButton = styled(DebugButton)`
|
||||||
|
|
@ -125,7 +130,9 @@ const StyledActionText = styled(Text)`
|
||||||
color: ${(props) => props.theme.colors.toast.undoRedoColor} !important;
|
color: ${(props) => props.theme.colors.toast.undoRedoColor} !important;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function ToastComponent(props: ToastProps & { undoAction?: () => void }) {
|
export function ToastComponent(
|
||||||
|
props: ToastProps & { undoAction?: () => void },
|
||||||
|
) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -134,8 +141,9 @@ function ToastComponent(props: ToastProps & { undoAction?: () => void }) {
|
||||||
dispatchableAction={props.dispatchableAction}
|
dispatchableAction={props.dispatchableAction}
|
||||||
isUndo={!!props.onUndo}
|
isUndo={!!props.onUndo}
|
||||||
variant={props.variant || Variant.info}
|
variant={props.variant || Variant.info}
|
||||||
|
width={props.width}
|
||||||
>
|
>
|
||||||
<FlexContainer>
|
<FlexContainer style={{ minWidth: 0 }}>
|
||||||
{props.variant === Variant.success ? (
|
{props.variant === Variant.success ? (
|
||||||
<Icon fillColor={Colors.GREEN} name="success" size={IconSize.XXL} />
|
<Icon fillColor={Colors.GREEN} name="success" size={IconSize.XXL} />
|
||||||
) : props.variant === Variant.warning ? (
|
) : props.variant === Variant.warning ? (
|
||||||
|
|
@ -145,10 +153,13 @@ function ToastComponent(props: ToastProps & { undoAction?: () => void }) {
|
||||||
<Icon name="error" size={IconSize.XXL} />
|
<Icon name="error" size={IconSize.XXL} />
|
||||||
) : null}
|
) : null}
|
||||||
<ToastTextWrapper>
|
<ToastTextWrapper>
|
||||||
<Text type={TextType.P1}>{props.text}</Text>
|
<Text className={props.contentClassName} type={TextType.P1}>
|
||||||
|
{props.text}
|
||||||
|
</Text>
|
||||||
{props.actionElement && (
|
{props.actionElement && (
|
||||||
<StyledActionText type={TextType.P1}>
|
<StyledActionText type={TextType.P1}>
|
||||||
{props.actionElement}
|
{!props.hideActionElementSpace ? <> </> : ""}
|
||||||
|
{props.actionElement}
|
||||||
</StyledActionText>
|
</StyledActionText>
|
||||||
)}
|
)}
|
||||||
{props.variant === Variant.danger && props.showDebugButton ? (
|
{props.variant === Variant.danger && props.showDebugButton ? (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
import React, { useState, useRef, RefObject, useCallback } from "react";
|
import React, {
|
||||||
import { connect, useSelector } from "react-redux";
|
useState,
|
||||||
|
useRef,
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
} from "react";
|
||||||
|
import { connect, useDispatch, useSelector } from "react-redux";
|
||||||
import { withRouter, RouteComponentProps } from "react-router";
|
import { withRouter, RouteComponentProps } from "react-router";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
|
|
@ -35,6 +41,7 @@ import { DebugButton } from "./Debugger/DebugCTA";
|
||||||
import EntityDeps from "./Debugger/EntityDependecies";
|
import EntityDeps from "./Debugger/EntityDependecies";
|
||||||
import Button, { Size } from "components/ads/Button";
|
import Button, { Size } from "components/ads/Button";
|
||||||
import { getActionTabsInitialIndex } from "selectors/editorSelectors";
|
import { getActionTabsInitialIndex } from "selectors/editorSelectors";
|
||||||
|
import { setActionTabsInitialIndex } from "actions/pluginActionActions";
|
||||||
|
|
||||||
type TextStyleProps = {
|
type TextStyleProps = {
|
||||||
accent: "primary" | "secondary" | "error";
|
accent: "primary" | "secondary" | "error";
|
||||||
|
|
@ -225,6 +232,7 @@ function ApiResponseView(props: Props) {
|
||||||
hasFailed = response.statusCode ? response.statusCode[0] !== "2" : false;
|
hasFailed = response.statusCode ? response.statusCode[0] !== "2" : false;
|
||||||
}
|
}
|
||||||
const panelRef: RefObject<HTMLDivElement> = useRef(null);
|
const panelRef: RefObject<HTMLDivElement> = useRef(null);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const onDebugClick = useCallback(() => {
|
const onDebugClick = useCallback(() => {
|
||||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||||
|
|
@ -321,6 +329,17 @@ function ApiResponseView(props: Props) {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedIndex !== initialIndex) setSelectedIndex(initialIndex);
|
||||||
|
}, [initialIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// reset on unmount
|
||||||
|
return () => {
|
||||||
|
dispatch(setActionTabsInitialIndex(0));
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onTabSelect = (index: number) => {
|
const onTabSelect = (index: number) => {
|
||||||
const debuggerTabKeys = ["ERROR", "LOGS"];
|
const debuggerTabKeys = ["ERROR", "LOGS"];
|
||||||
if (
|
if (
|
||||||
|
|
@ -331,7 +350,7 @@ function ApiResponseView(props: Props) {
|
||||||
tabName: tabs[index].key,
|
tabName: tabs[index].key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
dispatch(setActionTabsInitialIndex(index));
|
||||||
setSelectedIndex(index);
|
setSelectedIndex(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Classes } from "components/ads/common";
|
|
||||||
import Icon, { IconSize } from "components/ads/Icon";
|
import Icon, { IconSize } from "components/ads/Icon";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
|
|
@ -10,55 +9,17 @@ import { showDebugger as showDebuggerAction } from "actions/debuggerActions";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
import { getTypographyByKey } from "constants/DefaultTheme";
|
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||||
import { Layers } from "constants/Layers";
|
|
||||||
import { stopEventPropagation } from "utils/AppsmithUtils";
|
import { stopEventPropagation } from "utils/AppsmithUtils";
|
||||||
import { getMessageCount } from "selectors/debuggerSelectors";
|
import { getMessageCount } from "selectors/debuggerSelectors";
|
||||||
import getFeatureFlags from "utils/featureFlags";
|
import { setActionTabsInitialIndex } from "actions/pluginActionActions";
|
||||||
|
import {
|
||||||
const Container = styled.div<{ errorCount: number; warningCount: number }>`
|
matchApiPath,
|
||||||
z-index: ${Layers.debugger};
|
matchBuilderPath,
|
||||||
background-color: ${(props) =>
|
matchQueryPath,
|
||||||
props.theme.colors.debugger.floatingButton.background};
|
} from "constants/routes";
|
||||||
position: absolute;
|
import TooltipComponent from "components/ads/Tooltip";
|
||||||
right: 20px;
|
|
||||||
bottom: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: ${(props) => props.theme.spaces[6]}px;
|
|
||||||
color: ${(props) => props.theme.colors.debugger.floatingButton.color};
|
|
||||||
border-radius: 50px;
|
|
||||||
box-shadow: ${(props) => props.theme.colors.debugger.floatingButton.shadow};
|
|
||||||
|
|
||||||
.${Classes.ICON} {
|
|
||||||
&:hover {
|
|
||||||
path {
|
|
||||||
fill: ${(props) => props.theme.colors.icon.normal};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.debugger-count {
|
|
||||||
color: ${Colors.WHITE};
|
|
||||||
${(props) => getTypographyByKey(props, "h6")}
|
|
||||||
height: 16px;
|
|
||||||
padding: ${(props) => props.theme.spaces[1]}px;
|
|
||||||
background-color: ${(props) =>
|
|
||||||
props.errorCount + props.warningCount > 0
|
|
||||||
? props.errorCount === 0
|
|
||||||
? props.theme.colors.debugger.floatingButton.warningCount
|
|
||||||
: props.theme.colors.debugger.floatingButton.errorCount
|
|
||||||
: props.theme.colors.debugger.floatingButton.noErrorCount};
|
|
||||||
border-radius: 10px;
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function Debugger() {
|
function Debugger() {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const messageCounters = useSelector(getMessageCount);
|
const messageCounters = useSelector(getMessageCount);
|
||||||
|
|
||||||
const totalMessageCount = messageCounters.errors + messageCounters.warnings;
|
const totalMessageCount = messageCounters.errors + messageCounters.warnings;
|
||||||
|
|
@ -66,30 +27,6 @@ function Debugger() {
|
||||||
(state: AppState) => state.ui.debugger.isOpen,
|
(state: AppState) => state.ui.debugger.isOpen,
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClick = (e: any) => {
|
|
||||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
|
||||||
source: "CANVAS",
|
|
||||||
});
|
|
||||||
dispatch(showDebuggerAction(true));
|
|
||||||
stopEventPropagation(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!showDebugger && !getFeatureFlags().GIT)
|
|
||||||
return (
|
|
||||||
<Container
|
|
||||||
className="t--debugger"
|
|
||||||
errorCount={messageCounters.errors}
|
|
||||||
onClick={onClick}
|
|
||||||
warningCount={messageCounters.warnings}
|
|
||||||
>
|
|
||||||
<Icon name="bug" size={IconSize.XL} />
|
|
||||||
{!!messageCounters.errors && (
|
|
||||||
<div className="debugger-count t--debugger-count">
|
|
||||||
{totalMessageCount}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
return showDebugger ? (
|
return showDebugger ? (
|
||||||
<DebuggerTabs defaultIndex={totalMessageCount ? 0 : 1} />
|
<DebuggerTabs defaultIndex={totalMessageCount ? 0 : 1} />
|
||||||
) : null;
|
) : null;
|
||||||
|
|
@ -103,7 +40,7 @@ const TriggerContainer = styled.div<{
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: ${(props) => props.theme.spaces[9]}px;
|
margin-right: ${(props) => props.theme.spaces[10]}px;
|
||||||
|
|
||||||
.debugger-count {
|
.debugger-count {
|
||||||
color: ${Colors.WHITE};
|
color: ${Colors.WHITE};
|
||||||
|
|
@ -122,6 +59,7 @@ const TriggerContainer = styled.div<{
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 100%;
|
left: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -136,20 +74,44 @@ export function DebuggerTrigger() {
|
||||||
const totalMessageCount = messageCounters.errors + messageCounters.warnings;
|
const totalMessageCount = messageCounters.errors + messageCounters.warnings;
|
||||||
|
|
||||||
const onClick = (e: any) => {
|
const onClick = (e: any) => {
|
||||||
|
const isOnCanvas = matchBuilderPath(window.location.pathname);
|
||||||
|
if (isOnCanvas) {
|
||||||
|
dispatch(showDebuggerAction(!showDebugger));
|
||||||
if (!showDebugger)
|
if (!showDebugger)
|
||||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||||
source: "CANVAS",
|
source: "CANVAS",
|
||||||
});
|
});
|
||||||
dispatch(showDebuggerAction(!showDebugger));
|
}
|
||||||
|
|
||||||
|
const onApiEditor = matchApiPath(window.location.pathname);
|
||||||
|
const onQueryEditor = matchQueryPath(window.location.pathname);
|
||||||
|
if (onApiEditor || onQueryEditor) {
|
||||||
|
dispatch(setActionTabsInitialIndex(1));
|
||||||
|
}
|
||||||
stopEventPropagation(e);
|
stopEventPropagation(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tooltipContent =
|
||||||
|
totalMessageCount > 0
|
||||||
|
? `View details for ${totalMessageCount} ${
|
||||||
|
totalMessageCount > 1 ? "errors" : "error"
|
||||||
|
}`
|
||||||
|
: "View logs";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TriggerContainer
|
<TriggerContainer
|
||||||
|
className="t--debugger"
|
||||||
errorCount={messageCounters.errors}
|
errorCount={messageCounters.errors}
|
||||||
warningCount={messageCounters.warnings}
|
warningCount={messageCounters.warnings}
|
||||||
|
>
|
||||||
|
<TooltipComponent
|
||||||
|
content={tooltipContent}
|
||||||
|
modifiers={{
|
||||||
|
preventOverflow: { enabled: true },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="bug" onClick={onClick} size={IconSize.XL} />
|
<Icon name="bug" onClick={onClick} size={IconSize.XL} />
|
||||||
|
</TooltipComponent>
|
||||||
{!!messageCounters.errors && (
|
{!!messageCounters.errors && (
|
||||||
<div className="debugger-count t--debugger-count">
|
<div className="debugger-count t--debugger-count">
|
||||||
{totalMessageCount > 9 ? "9+" : totalMessageCount}
|
{totalMessageCount > 9 ? "9+" : totalMessageCount}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ export const Layers = {
|
||||||
max: Indices.LayerMax,
|
max: Indices.LayerMax,
|
||||||
sideStickyBar: Indices.Layer7,
|
sideStickyBar: Indices.Layer7,
|
||||||
evaluationPopper: Indices.Layer3,
|
evaluationPopper: Indices.Layer3,
|
||||||
|
concurrentEditorWarning: Indices.Layer2,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayersContext = React.createContext(Layers);
|
export const LayersContext = React.createContext(Layers);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ export const ReduxSagaChannels = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReduxActionTypes = {
|
export const ReduxActionTypes = {
|
||||||
|
APP_COLLAB_SET_CONCURRENT_PAGE_EDITORS:
|
||||||
|
"APP_COLLAB_SET_CONCURRENT_PAGE_EDITORS",
|
||||||
FETCH_SSH_KEY_PAIR_INIT: "FETCH_SSH_KEY_PAIR_INIT",
|
FETCH_SSH_KEY_PAIR_INIT: "FETCH_SSH_KEY_PAIR_INIT",
|
||||||
FETCH_SSH_KEY_PAIR_SUCCESS: "FETCH_SSH_KEY_PAIR_SUCCESS",
|
FETCH_SSH_KEY_PAIR_SUCCESS: "FETCH_SSH_KEY_PAIR_SUCCESS",
|
||||||
SET_IS_IMPORT_APP_VIA_GIT_MODAL_OPEN: "SET_IS_IMPORT_APP_VIA_GIT_MODAL_OPEN",
|
SET_IS_IMPORT_APP_VIA_GIT_MODAL_OPEN: "SET_IS_IMPORT_APP_VIA_GIT_MODAL_OPEN",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import styled from "styled-components";
|
||||||
import QuickGitActions from "pages/Editor/gitSync/QuickGitActions";
|
import QuickGitActions from "pages/Editor/gitSync/QuickGitActions";
|
||||||
import { Layers } from "constants/Layers";
|
import { Layers } from "constants/Layers";
|
||||||
import { DebuggerTrigger } from "components/editorComponents/Debugger";
|
import { DebuggerTrigger } from "components/editorComponents/Debugger";
|
||||||
|
import { Colors } from "constants/Colors";
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -12,6 +13,7 @@ const Container = styled.div`
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background-color: ${(props) => props.theme.colors.editorBottomBar.background};
|
background-color: ${(props) => props.theme.colors.editorBottomBar.background};
|
||||||
z-index: ${Layers.bottomBar};
|
z-index: ${Layers.bottomBar};
|
||||||
|
border-top: solid 1px ${Colors.MERCURY};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function BottomBar() {
|
export default function BottomBar() {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import WidgetsEditor from "./WidgetsEditor";
|
||||||
import Sidebar from "components/editorComponents/Sidebar";
|
import Sidebar from "components/editorComponents/Sidebar";
|
||||||
import BottomBar from "./BottomBar";
|
import BottomBar from "./BottomBar";
|
||||||
|
|
||||||
import getFeatureFlags from "utils/featureFlags";
|
|
||||||
|
|
||||||
import { BUILDER_CHECKLIST_URL, BUILDER_URL } from "constants/routes";
|
import { BUILDER_CHECKLIST_URL, BUILDER_URL } from "constants/routes";
|
||||||
import OnboardingChecklist from "./FirstTimeUserOnboarding/Checklist";
|
import OnboardingChecklist from "./FirstTimeUserOnboarding/Checklist";
|
||||||
const SentryRoute = Sentry.withSentryRouting(Route);
|
const SentryRoute = Sentry.withSentryRouting(Route);
|
||||||
|
|
@ -17,8 +15,7 @@ const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(
|
height: calc(
|
||||||
100vh - ${(props) => props.theme.smallHeaderHeight} -
|
100vh - ${(props) => props.theme.smallHeaderHeight} -
|
||||||
${(props) =>
|
${(props) => props.theme.bottomBarHeight}
|
||||||
getFeatureFlags().GIT ? props.theme.bottomBarHeight : "0px"}
|
|
||||||
);
|
);
|
||||||
background-color: ${(props) => props.theme.appBackground};
|
background-color: ${(props) => props.theme.appBackground};
|
||||||
`;
|
`;
|
||||||
|
|
@ -47,7 +44,7 @@ function MainContainer() {
|
||||||
</Switch>
|
</Switch>
|
||||||
</EditorContainer>
|
</EditorContainer>
|
||||||
</Container>
|
</Container>
|
||||||
{getFeatureFlags().GIT && <BottomBar />}
|
<BottomBar />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { RefObject, useRef, useState } from "react";
|
import React, { RefObject, useEffect, useRef, useState } from "react";
|
||||||
import { InjectedFormProps } from "redux-form";
|
import { InjectedFormProps } from "redux-form";
|
||||||
import { Icon, Tag } from "@blueprintjs/core";
|
import { Icon, Tag } from "@blueprintjs/core";
|
||||||
import { isString } from "lodash";
|
import { isString } from "lodash";
|
||||||
|
|
@ -71,6 +71,7 @@ import TooltipComponent from "components/ads/Tooltip";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
import SearchSnippets from "components/ads/SnippetButton";
|
import SearchSnippets from "components/ads/SnippetButton";
|
||||||
|
import { setActionTabsInitialIndex } from "actions/pluginActionActions";
|
||||||
|
|
||||||
const QueryFormContainer = styled.form`
|
const QueryFormContainer = styled.form`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
@ -430,6 +431,17 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
window.innerHeight,
|
window.innerHeight,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedIndex !== initialIndex) setSelectedIndex(initialIndex);
|
||||||
|
}, [initialIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// reset on unmount
|
||||||
|
return () => {
|
||||||
|
dispatch(setActionTabsInitialIndex(0));
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const params = useParams<{ apiId?: string; queryId?: string }>();
|
const params = useParams<{ apiId?: string; queryId?: string }>();
|
||||||
|
|
||||||
const actions: Action[] = useSelector((state: AppState) =>
|
const actions: Action[] = useSelector((state: AppState) =>
|
||||||
|
|
@ -729,7 +741,7 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
tabName: responseTabs[index].key,
|
tabName: responseTabs[index].key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
dispatch(setActionTabsInitialIndex(index));
|
||||||
setSelectedIndex(index);
|
setSelectedIndex(index);
|
||||||
};
|
};
|
||||||
const { entityDependencies, hasDependencies } = useEntityDependencies(
|
const { entityDependencies, hasDependencies } = useEntityDependencies(
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
collabStopEditingAppEvent,
|
collabStopEditingAppEvent,
|
||||||
collabResetAppEditors,
|
collabResetAppEditors,
|
||||||
} from "actions/appCollabActions";
|
} from "actions/appCollabActions";
|
||||||
|
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||||
import { getIsAppLevelSocketConnected } from "selectors/websocketSelectors";
|
import { getIsAppLevelSocketConnected } from "selectors/websocketSelectors";
|
||||||
|
|
||||||
const UserImageContainer = styled.div`
|
const UserImageContainer = styled.div`
|
||||||
|
|
@ -37,6 +38,8 @@ export function useEditAppCollabEvents(applicationId?: string) {
|
||||||
|
|
||||||
const isWebsocketConnected = useSelector(getIsAppLevelSocketConnected);
|
const isWebsocketConnected = useSelector(getIsAppLevelSocketConnected);
|
||||||
|
|
||||||
|
const currentPageId = useSelector(getCurrentPageId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// websocket has to be connected as we only fire this event once.
|
// websocket has to be connected as we only fire this event once.
|
||||||
isWebsocketConnected &&
|
isWebsocketConnected &&
|
||||||
|
|
@ -48,7 +51,7 @@ export function useEditAppCollabEvents(applicationId?: string) {
|
||||||
applicationId &&
|
applicationId &&
|
||||||
dispatch(collabStopEditingAppEvent(applicationId));
|
dispatch(collabStopEditingAppEvent(applicationId));
|
||||||
};
|
};
|
||||||
}, [applicationId, isWebsocketConnected]);
|
}, [applicationId, currentPageId, isWebsocketConnected]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RealtimeAppEditors(props: RealtimeAppEditorsProps) {
|
function RealtimeAppEditors(props: RealtimeAppEditorsProps) {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { ReactComponent as GitCommitLine } from "assets/icons/ads/git-commit-lin
|
||||||
import Button, { Category, Size } from "components/ads/Button";
|
import Button, { Category, Size } from "components/ads/Button";
|
||||||
import { setIsGitSyncModalOpen } from "actions/gitSyncActions";
|
import { setIsGitSyncModalOpen } from "actions/gitSyncActions";
|
||||||
import { GitSyncModalTab } from "entities/GitSync";
|
import { GitSyncModalTab } from "entities/GitSync";
|
||||||
|
import getFeatureFlags from "utils/featureFlags";
|
||||||
|
|
||||||
type QuickActionButtonProps = {
|
type QuickActionButtonProps = {
|
||||||
count?: number;
|
count?: number;
|
||||||
|
|
@ -119,6 +120,23 @@ const Container = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-left: ${(props) => props.theme.spaces[10]}px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledIcon = styled(GitCommitLine)`
|
||||||
|
& path {
|
||||||
|
fill: ${Colors.DARK_GRAY};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PlaceholderButton = styled.div`
|
||||||
|
padding: ${(props) =>
|
||||||
|
`${props.theme.spaces[1]}px ${props.theme.spaces[3]}px`};
|
||||||
|
border: solid 1px ${Colors.MERCURY};
|
||||||
|
${(props) => getTypographyByKey(props, "btnSmall")};
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: ${Colors.ALABASTER_ALT};
|
||||||
|
color: ${Colors.GRAY};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function ConnectGitPlaceholder() {
|
function ConnectGitPlaceholder() {
|
||||||
|
|
@ -126,7 +144,21 @@ function ConnectGitPlaceholder() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<GitCommitLine />
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<div>It's not live for you yet</div>
|
||||||
|
<div>Coming soon!</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
disabled={getFeatureFlags().GIT}
|
||||||
|
modifiers={{
|
||||||
|
preventOverflow: { enabled: true },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container style={{ marginLeft: 0, cursor: "pointer" }}>
|
||||||
|
<StyledIcon />
|
||||||
|
{getFeatureFlags().GIT ? (
|
||||||
<Button
|
<Button
|
||||||
category={Category.tertiary}
|
category={Category.tertiary}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -135,6 +167,11 @@ function ConnectGitPlaceholder() {
|
||||||
size={Size.small}
|
size={Size.small}
|
||||||
text={createMessage(CONNECT_GIT)}
|
text={createMessage(CONNECT_GIT)}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<PlaceholderButton>{createMessage(CONNECT_GIT)}</PlaceholderButton>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</Tooltip>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +200,7 @@ export default function QuickGitActions() {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return isGitRepoSetup ? (
|
return getFeatureFlags().GIT && isGitRepoSetup ? (
|
||||||
<Container>
|
<Container>
|
||||||
<BranchButton />
|
<BranchButton />
|
||||||
{quickActionButtons.map((button) => (
|
{quickActionButtons.map((button) => (
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,13 @@ import GitSyncModal from "pages/Editor/gitSync/GitSyncModal";
|
||||||
import history from "utils/history";
|
import history from "utils/history";
|
||||||
import { fetchPage, updateCurrentPage } from "actions/pageActions";
|
import { fetchPage, updateCurrentPage } from "actions/pageActions";
|
||||||
|
|
||||||
|
import ConcurrentPageEditorToast from "comments/ConcurrentPageEditorToast";
|
||||||
|
import { getIsPageLevelSocketConnected } from "selectors/websocketSelectors";
|
||||||
|
import {
|
||||||
|
collabStartSharingPointerEvent,
|
||||||
|
collabStopSharingPointerEvent,
|
||||||
|
} from "actions/appCollabActions";
|
||||||
|
|
||||||
type EditorProps = {
|
type EditorProps = {
|
||||||
currentApplicationId?: string;
|
currentApplicationId?: string;
|
||||||
currentApplicationName?: string;
|
currentApplicationName?: string;
|
||||||
|
|
@ -52,6 +59,9 @@ type EditorProps = {
|
||||||
handlePathUpdated: (location: typeof window.location) => void;
|
handlePathUpdated: (location: typeof window.location) => void;
|
||||||
fetchPage: (pageId: string) => void;
|
fetchPage: (pageId: string) => void;
|
||||||
updateCurrentPage: (pageId: string) => void;
|
updateCurrentPage: (pageId: string) => void;
|
||||||
|
isPageLevelSocketConnected: boolean;
|
||||||
|
collabStartSharingPointerEvent: (pageId: string) => void;
|
||||||
|
collabStopSharingPointerEvent: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = EditorProps & RouteComponentProps<BuilderRouteParams>;
|
type Props = EditorProps & RouteComponentProps<BuilderRouteParams>;
|
||||||
|
|
@ -73,6 +83,10 @@ class Editor extends Component<Props> {
|
||||||
}
|
}
|
||||||
this.props.handlePathUpdated(window.location);
|
this.props.handlePathUpdated(window.location);
|
||||||
this.unlisten = history.listen(this.handleHistoryChange);
|
this.unlisten = history.listen(this.handleHistoryChange);
|
||||||
|
|
||||||
|
if (this.props.isPageLevelSocketConnected && pageId) {
|
||||||
|
this.props.collabStartSharingPointerEvent(pageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Props, nextState: { registered: boolean }) {
|
shouldComponentUpdate(nextProps: Props, nextState: { registered: boolean }) {
|
||||||
|
|
@ -88,22 +102,30 @@ class Editor extends Component<Props> {
|
||||||
this.props.isEditorInitializeError ||
|
this.props.isEditorInitializeError ||
|
||||||
nextProps.creatingOnboardingDatabase !==
|
nextProps.creatingOnboardingDatabase !==
|
||||||
this.props.creatingOnboardingDatabase ||
|
this.props.creatingOnboardingDatabase ||
|
||||||
nextState.registered !== this.state.registered
|
nextState.registered !== this.state.registered ||
|
||||||
|
(nextProps.isPageLevelSocketConnected &&
|
||||||
|
!this.props.isPageLevelSocketConnected)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props) {
|
componentDidUpdate(prevProps: Props) {
|
||||||
const { pageId } = this.props.match.params || {};
|
const { pageId } = this.props.match.params || {};
|
||||||
const { pageId: prevPageId } = prevProps.match.params || {};
|
const { pageId: prevPageId } = prevProps.match.params || {};
|
||||||
if (pageId && pageId !== prevPageId) {
|
const isPageIdUpdated = pageId !== prevPageId;
|
||||||
|
if (pageId && isPageIdUpdated) {
|
||||||
this.props.updateCurrentPage(pageId);
|
this.props.updateCurrentPage(pageId);
|
||||||
this.props.fetchPage(pageId);
|
this.props.fetchPage(pageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.isPageLevelSocketConnected && isPageIdUpdated) {
|
||||||
|
this.props.collabStartSharingPointerEvent(pageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.resetEditorRequest();
|
this.props.resetEditorRequest();
|
||||||
if (typeof this.unlisten === "function") this.unlisten();
|
if (typeof this.unlisten === "function") this.unlisten();
|
||||||
|
this.props.collabStopSharingPointerEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHistoryChange = (location: any) => {
|
handleHistoryChange = (location: any) => {
|
||||||
|
|
@ -143,6 +165,7 @@ class Editor extends Component<Props> {
|
||||||
<AddCommentTourComponent />
|
<AddCommentTourComponent />
|
||||||
<CommentShowCaseCarousel />
|
<CommentShowCaseCarousel />
|
||||||
<GitSyncModal />
|
<GitSyncModal />
|
||||||
|
<ConcurrentPageEditorToast />
|
||||||
</GlobalHotKeys>
|
</GlobalHotKeys>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmRunModal />
|
<ConfirmRunModal />
|
||||||
|
|
@ -163,6 +186,7 @@ const mapStateToProps = (state: AppState) => ({
|
||||||
user: getCurrentUser(state),
|
user: getCurrentUser(state),
|
||||||
creatingOnboardingDatabase: state.ui.onBoarding.showOnboardingLoader,
|
creatingOnboardingDatabase: state.ui.onBoarding.showOnboardingLoader,
|
||||||
currentApplicationName: state.ui.applications.currentApplication?.name,
|
currentApplicationName: state.ui.applications.currentApplication?.name,
|
||||||
|
isPageLevelSocketConnected: getIsPageLevelSocketConnected(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any) => {
|
const mapDispatchToProps = (dispatch: any) => {
|
||||||
|
|
@ -174,6 +198,10 @@ const mapDispatchToProps = (dispatch: any) => {
|
||||||
dispatch(handlePathUpdated(location)),
|
dispatch(handlePathUpdated(location)),
|
||||||
fetchPage: (pageId: string) => dispatch(fetchPage(pageId)),
|
fetchPage: (pageId: string) => dispatch(fetchPage(pageId)),
|
||||||
updateCurrentPage: (pageId: string) => dispatch(updateCurrentPage(pageId)),
|
updateCurrentPage: (pageId: string) => dispatch(updateCurrentPage(pageId)),
|
||||||
|
collabStartSharingPointerEvent: (pageId: string) =>
|
||||||
|
dispatch(collabStartSharingPointerEvent(pageId)),
|
||||||
|
collabStopSharingPointerEvent: () =>
|
||||||
|
dispatch(collabStopSharingPointerEvent()),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { getDependenciesFromInverseDependencies } from "components/editorCompone
|
||||||
import _, { debounce } from "lodash";
|
import _, { debounce } from "lodash";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import ResizeObserver from "resize-observer-polyfill";
|
import ResizeObserver from "resize-observer-polyfill";
|
||||||
import getFeatureFlags from "utils/featureFlags";
|
|
||||||
|
|
||||||
export const draggableElement = (
|
export const draggableElement = (
|
||||||
id: string,
|
id: string,
|
||||||
|
|
@ -41,7 +40,7 @@ export const draggableElement = (
|
||||||
calculatedLeft: number,
|
calculatedLeft: number,
|
||||||
calculatedTop: number,
|
calculatedTop: number,
|
||||||
) => {
|
) => {
|
||||||
const bottomBarOffset = getFeatureFlags().GIT ? 34 : 0;
|
const bottomBarOffset = 34;
|
||||||
|
|
||||||
if (calculatedLeft <= 0) {
|
if (calculatedLeft <= 0) {
|
||||||
calculatedLeft = 0;
|
calculatedLeft = 0;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { cloneDeep } from "lodash";
|
||||||
const initialState: AppCollabReducerState = {
|
const initialState: AppCollabReducerState = {
|
||||||
editors: [],
|
editors: [],
|
||||||
pointerData: {},
|
pointerData: {},
|
||||||
|
pageEditors: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const appCollabReducer = createReducer(initialState, {
|
const appCollabReducer = createReducer(initialState, {
|
||||||
|
|
@ -51,6 +52,13 @@ const appCollabReducer = createReducer(initialState, {
|
||||||
pointerData: {},
|
pointerData: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
[ReduxActionTypes.APP_COLLAB_SET_CONCURRENT_PAGE_EDITORS]: (
|
||||||
|
state: AppCollabReducerState,
|
||||||
|
action: ReduxAction<any>,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
pageEditors: action.payload,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
type PointerDataType = {
|
type PointerDataType = {
|
||||||
|
|
@ -60,6 +68,7 @@ type PointerDataType = {
|
||||||
export type AppCollabReducerState = {
|
export type AppCollabReducerState = {
|
||||||
editors: User[];
|
editors: User[];
|
||||||
pointerData: PointerDataType;
|
pointerData: PointerDataType;
|
||||||
|
pageEditors: User[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default appCollabReducer;
|
export default appCollabReducer;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import { put, select } from "redux-saga/effects";
|
import { put, select } from "redux-saga/effects";
|
||||||
import {
|
import { APP_LEVEL_SOCKET_EVENTS } from "./socketEvents";
|
||||||
APP_LEVEL_SOCKET_EVENTS,
|
|
||||||
PAGE_LEVEL_SOCKET_EVENTS,
|
|
||||||
} from "./socketEvents";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
newCommentEvent,
|
newCommentEvent,
|
||||||
|
|
@ -96,7 +93,7 @@ export default function* handleAppLevelSocketEvents(event: any) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Collab V2 - Realtime Editing
|
// Collab V2 - Realtime Editing
|
||||||
case PAGE_LEVEL_SOCKET_EVENTS.LIST_ONLINE_APP_EDITORS: {
|
case APP_LEVEL_SOCKET_EVENTS.LIST_ONLINE_APP_EDITORS: {
|
||||||
yield put(collabSetAppEditors(event.payload[0]));
|
yield put(collabSetAppEditors(event.payload[0]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { PAGE_LEVEL_SOCKET_EVENTS } from "./socketEvents";
|
||||||
import {
|
import {
|
||||||
collabSetEditorsPointersData,
|
collabSetEditorsPointersData,
|
||||||
collabUnsetEditorsPointersData,
|
collabUnsetEditorsPointersData,
|
||||||
|
collabConcurrentPageEditorsData,
|
||||||
} from "actions/appCollabActions";
|
} from "actions/appCollabActions";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
|
|
@ -19,6 +20,11 @@ export default function* handlePageLevelSocketEvents(event: any, socket: any) {
|
||||||
yield put(collabUnsetEditorsPointersData(event.payload[0]));
|
yield put(collabUnsetEditorsPointersData(event.payload[0]));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case PAGE_LEVEL_SOCKET_EVENTS.LIST_ONLINE_PAGE_EDITORS: {
|
||||||
|
yield put(collabConcurrentPageEditorsData(event.payload[0]?.users));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Sentry.captureException(e);
|
Sentry.captureException(e);
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ export const APP_LEVEL_SOCKET_EVENTS = {
|
||||||
|
|
||||||
// notification events
|
// notification events
|
||||||
INSERT_NOTIFICATION: "insert:notification",
|
INSERT_NOTIFICATION: "insert:notification",
|
||||||
|
|
||||||
|
LIST_ONLINE_APP_EDITORS: "collab:online_editors", // user presence
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PAGE_LEVEL_SOCKET_EVENTS = {
|
export const PAGE_LEVEL_SOCKET_EVENTS = {
|
||||||
START_EDITING_APP: "collab:start_edit",
|
START_EDITING_APP: "collab:start_edit",
|
||||||
STOP_EDITING_APP: "collab:leave_edit",
|
STOP_EDITING_APP: "collab:leave_edit",
|
||||||
LIST_ONLINE_APP_EDITORS: "collab:online_editors", // user presence
|
LIST_ONLINE_PAGE_EDITORS: "collab:online_editors",
|
||||||
SHARE_USER_POINTER: "collab:mouse_pointer", // multi pointer
|
SHARE_USER_POINTER: "collab:mouse_pointer", // multi pointer
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { AppState } from "reducers";
|
||||||
import { AppCollabReducerState } from "reducers/uiReducers/appCollabReducer";
|
import { AppCollabReducerState } from "reducers/uiReducers/appCollabReducer";
|
||||||
import { getCurrentUser } from "./usersSelectors";
|
import { getCurrentUser } from "./usersSelectors";
|
||||||
import getFeatureFlags from "../utils/featureFlags";
|
import getFeatureFlags from "../utils/featureFlags";
|
||||||
|
import { User } from "entities/AppCollab/CollabInterfaces";
|
||||||
|
import { ANONYMOUS_USERNAME } from "constants/userConstants";
|
||||||
|
|
||||||
export const getAppCollabState = (state: AppState) => state.ui.appCollab;
|
export const getAppCollabState = (state: AppState) => state.ui.appCollab;
|
||||||
|
|
||||||
|
|
@ -14,3 +16,22 @@ export const getRealtimeAppEditors = createSelector(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const isMultiplayerEnabledForUser = () => getFeatureFlags().MULTIPLAYER;
|
export const isMultiplayerEnabledForUser = () => getFeatureFlags().MULTIPLAYER;
|
||||||
|
|
||||||
|
export const getConcurrentPageEditors = (state: AppState) =>
|
||||||
|
state.ui.appCollab.pageEditors;
|
||||||
|
|
||||||
|
export const isConcurrentPageEditorToastVisible = createSelector(
|
||||||
|
getConcurrentPageEditors,
|
||||||
|
getCurrentUser,
|
||||||
|
(pageEditors: User[], currentUser?: User) => {
|
||||||
|
if (
|
||||||
|
pageEditors.length === 0 ||
|
||||||
|
!currentUser ||
|
||||||
|
currentUser.email === ANONYMOUS_USERNAME
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
return pageEditors.some(
|
||||||
|
(editor: User) => editor.email !== currentUser?.email,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ const STORAGE_KEYS: { [id: string]: string } = {
|
||||||
"FIRST_TIME_USER_ONBOARDING_APPLICATION_ID",
|
"FIRST_TIME_USER_ONBOARDING_APPLICATION_ID",
|
||||||
FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY:
|
FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY:
|
||||||
"FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY",
|
"FIRST_TIME_USER_ONBOARDING_INTRO_MODAL_VISIBILITY",
|
||||||
|
HIDE_CONCURRENT_EDITOR_WARNING_TOAST: "HIDE_CONCURRENT_EDITOR_WARNING_TOAST",
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = localforage.createInstance({
|
const store = localforage.createInstance({
|
||||||
|
|
@ -261,3 +262,32 @@ export const getFirstTimeUserOnboardingIntroModalVisibility = async () => {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hideConcurrentEditorWarningToast = async () => {
|
||||||
|
try {
|
||||||
|
await store.setItem(
|
||||||
|
STORAGE_KEYS.HIDE_CONCURRENT_EDITOR_WARNING_TOAST,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
log.error(
|
||||||
|
"An error occurred while setting HIDE_CONCURRENT_EDITOR_WARNING_TOAST",
|
||||||
|
);
|
||||||
|
log.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getIsConcurrentEditorWarningToastHidden = async () => {
|
||||||
|
try {
|
||||||
|
const flag = await store.getItem(
|
||||||
|
STORAGE_KEYS.HIDE_CONCURRENT_EDITOR_WARNING_TOAST,
|
||||||
|
);
|
||||||
|
return flag;
|
||||||
|
} catch (error) {
|
||||||
|
log.error(
|
||||||
|
"An error occurred while fetching HIDE_CONCURRENT_EDITOR_WARNING_TOAST",
|
||||||
|
);
|
||||||
|
log.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user