Revert "Onboarding flow (#1960)"
This reverts commit e84699e7ba.
Reverting this commit because this flow requires more changes before it's ready for prime-time.
Will continue development on this feature in a different branch
This commit is contained in:
parent
ea6e897dde
commit
a942dc198e
|
|
@ -1,67 +0,0 @@
|
||||||
const onboarding = require("../../../locators/Onboarding.json");
|
|
||||||
const explorer = require("../../../locators/explorerlocators.json");
|
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
|
||||||
const loginPage = require("../../../locators/LoginPage.json");
|
|
||||||
|
|
||||||
describe("Onboarding", function() {
|
|
||||||
it("Onboarding flow", function() {
|
|
||||||
cy.LogOut();
|
|
||||||
|
|
||||||
cy.visit("/user/signup");
|
|
||||||
cy.get("input[name='email']").type(Cypress.env("USERNAME"));
|
|
||||||
cy.get(loginPage.password).type(Cypress.env("PASSWORD"));
|
|
||||||
cy.get(loginPage.submitBtn).click();
|
|
||||||
|
|
||||||
cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD"));
|
|
||||||
|
|
||||||
cy.get(homePage.createNew)
|
|
||||||
.first()
|
|
||||||
.click({ force: true });
|
|
||||||
cy.wait("@createNewApplication").should(
|
|
||||||
"have.nested.property",
|
|
||||||
"response.body.responseMeta.status",
|
|
||||||
201,
|
|
||||||
);
|
|
||||||
cy.get("#loading").should("not.exist");
|
|
||||||
|
|
||||||
//Onboarding
|
|
||||||
cy.contains(".t--create-database", "Explore Appsmith").click();
|
|
||||||
|
|
||||||
cy.get(onboarding.tooltipAction).click();
|
|
||||||
|
|
||||||
// Add widget
|
|
||||||
cy.get(".t--add-widget").click();
|
|
||||||
cy.dragAndDropToCanvas("tablewidget", { x: 30, y: -30 });
|
|
||||||
|
|
||||||
cy.get(onboarding.tooltipSnippet).click({ force: true });
|
|
||||||
|
|
||||||
cy.get(".t--property-control-tabledata" + " .CodeMirror textarea")
|
|
||||||
.first()
|
|
||||||
.focus({ force: true })
|
|
||||||
.type("{uparrow}", { force: true })
|
|
||||||
.type("{ctrl}{shift}{downarrow}", { force: true });
|
|
||||||
cy.focused().then(() => {
|
|
||||||
cy.get(".t--property-control-tabledata" + " .CodeMirror")
|
|
||||||
.first()
|
|
||||||
.then(editor => {
|
|
||||||
editor[0].CodeMirror.setValue("{{ExampleQuery.data}}");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
cy.closePropertyPane();
|
|
||||||
cy.get(explorer.closeWidgets).click();
|
|
||||||
|
|
||||||
cy.openPropertyPane("tablewidget");
|
|
||||||
cy.get(onboarding.tooltipAction).click({ force: true });
|
|
||||||
|
|
||||||
cy.PublishtheApp();
|
|
||||||
cy.get(".t--continue-on-my-own").click();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
localStorage.removeItem("OnboardingState");
|
|
||||||
cy.window().then(window => {
|
|
||||||
window.indexedDB.deleteDatabase("Appsmith");
|
|
||||||
});
|
|
||||||
cy.log("Cleared");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -13,7 +13,6 @@ describe("Create new org and an app within the same", function() {
|
||||||
cy.createOrg(orgid);
|
cy.createOrg(orgid);
|
||||||
cy.CreateAppForOrg(orgid, appid);
|
cy.CreateAppForOrg(orgid, appid);
|
||||||
cy.NavigateToHome();
|
cy.NavigateToHome();
|
||||||
|
|
||||||
cy.CreateApp(appid);
|
cy.CreateApp(appid);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"tooltipAction": ".tooltip-action",
|
|
||||||
"tooltipSnippet": ".tooltip-snippet"
|
|
||||||
}
|
|
||||||
|
|
@ -249,7 +249,6 @@ Cypress.Commands.add("CreateApp", appname => {
|
||||||
);
|
);
|
||||||
cy.get("#loading").should("not.exist");
|
cy.get("#loading").should("not.exist");
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
|
|
||||||
cy.get(homePage.applicationName).type(appname + "{enter}");
|
cy.get(homePage.applicationName).type(appname + "{enter}");
|
||||||
cy.wait("@updateApplication").should(
|
cy.wait("@updateApplication").should(
|
||||||
"have.nested.property",
|
"have.nested.property",
|
||||||
|
|
@ -951,18 +950,11 @@ Cypress.Commands.add("PublishtheApp", () => {
|
||||||
// Wait before publish
|
// Wait before publish
|
||||||
cy.wait(2000);
|
cy.wait(2000);
|
||||||
cy.assertPageSave();
|
cy.assertPageSave();
|
||||||
|
|
||||||
// Stubbing window.open to open in the same tab
|
|
||||||
cy.window().then(window => {
|
|
||||||
cy.stub(window, "open").callsFake(url => {
|
|
||||||
window.location.href = Cypress.config().baseUrl + url.substring(1);
|
|
||||||
window.location.target = "_self";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get(homePage.publishButton).click();
|
cy.get(homePage.publishButton).click();
|
||||||
cy.wait("@publishApp");
|
cy.wait("@publishApp");
|
||||||
|
cy.get('a[class="bp3-button"]')
|
||||||
|
.invoke("removeAttr", "target")
|
||||||
|
.click({ force: true });
|
||||||
cy.url().should("include", "/pages");
|
cy.url().should("include", "/pages");
|
||||||
cy.log("pagename: " + localStorage.getItem("PageName"));
|
cy.log("pagename: " + localStorage.getItem("PageName"));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,6 @@
|
||||||
"localforage": "^1.7.3",
|
"localforage": "^1.7.3",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
"loglevel": "^1.6.7",
|
"loglevel": "^1.6.7",
|
||||||
"lottie-web": "^5.7.4",
|
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"moment-timezone": "^0.5.27",
|
"moment-timezone": "^0.5.27",
|
||||||
"nanoid": "^2.0.4",
|
"nanoid": "^2.0.4",
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
|
||||||
import { Action } from "entities/Action";
|
|
||||||
|
|
||||||
export const createOnboardingActionInit = (payload: Partial<Action>) => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.CREATE_ONBOARDING_ACTION_INIT,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createOnboardingActionSuccess = (payload: Action) => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.CREATE_ONBOARDING_ACTION_SUCCESS,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showTooltip = (payload: number) => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.SHOW_ONBOARDING_TOOLTIP,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const endOnboarding = () => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.END_ONBOARDING,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setCurrentStep = (payload: number) => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.SET_CURRENT_STEP,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setOnboardingState = (payload: boolean) => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.SET_ONBOARDING_STATE,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
DEFAULT_EXECUTE_ACTION_TIMEOUT_MS,
|
DEFAULT_EXECUTE_ACTION_TIMEOUT_MS,
|
||||||
} from "constants/ApiConstants";
|
} from "constants/ApiConstants";
|
||||||
import axios, { AxiosPromise, CancelTokenSource } from "axios";
|
import axios, { AxiosPromise, CancelTokenSource } from "axios";
|
||||||
import { Action, RestAction } from "entities/Action";
|
import { RestAction } from "entities/Action";
|
||||||
|
|
||||||
export interface CreateActionRequest<T> extends APIRequest {
|
export interface CreateActionRequest<T> extends APIRequest {
|
||||||
datasourceId: string;
|
datasourceId: string;
|
||||||
|
|
@ -114,7 +114,7 @@ class ActionAPI extends API {
|
||||||
}
|
}
|
||||||
|
|
||||||
static createAPI(
|
static createAPI(
|
||||||
apiConfig: Partial<Action>,
|
apiConfig: RestAction,
|
||||||
): AxiosPromise<ActionCreateUpdateResponse> {
|
): AxiosPromise<ActionCreateUpdateResponse> {
|
||||||
return API.post(ActionAPI.url, apiConfig);
|
return API.post(ActionAPI.url, apiConfig);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,30 +0,0 @@
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
|
||||||
import React, { ReactNode } from "react";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { getCurrentStep, inOnboarding } from "sagas/OnboardingSagas";
|
|
||||||
|
|
||||||
type BoxedProps = {
|
|
||||||
// child nodes are not visible until this step is reached
|
|
||||||
step: OnboardingStep;
|
|
||||||
// Any additional conditions to hide the children
|
|
||||||
show?: boolean;
|
|
||||||
children: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Boxed(or hidden).
|
|
||||||
const Boxed: React.FC<BoxedProps> = (props: BoxedProps) => {
|
|
||||||
const currentStep = useSelector(getCurrentStep);
|
|
||||||
const onboarding = useSelector(inOnboarding);
|
|
||||||
|
|
||||||
if (onboarding && currentStep < props.step && !props.show) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>{props.children}</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
Boxed.defaultProps = {
|
|
||||||
show: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Boxed;
|
|
||||||
|
|
@ -1,213 +0,0 @@
|
||||||
import { Dialog, Icon } from "@blueprintjs/core";
|
|
||||||
import { IconWrapper } from "constants/IconConstants";
|
|
||||||
import { DATA_SOURCES_EDITOR_URL } from "constants/routes";
|
|
||||||
import { HeaderIcons } from "icons/HeaderIcons";
|
|
||||||
import AppInviteUsersForm from "pages/organization/AppInviteUsersForm";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import { useSelector } from "store";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import { ReactComponent as BookIcon } from "assets/icons/ads/app-icons/book.svg";
|
|
||||||
import { FormDialogComponent } from "../form/FormDialogComponent";
|
|
||||||
import { getCurrentOrgId } from "selectors/organizationSelectors";
|
|
||||||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
|
||||||
import { getCurrentPageId } from "selectors/editorSelectors";
|
|
||||||
import { endOnboarding } from "actions/onboardingActions";
|
|
||||||
import { getQueryParams } from "utils/AppsmithUtils";
|
|
||||||
import { getOnboardingState } from "utils/storage";
|
|
||||||
|
|
||||||
const StyledDialog = styled(Dialog)`
|
|
||||||
&& {
|
|
||||||
width: 850px;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ApplicationPublishedWrapper = styled.div`
|
|
||||||
padding: 33px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Title = styled.div`
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 36px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ContentWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
margin-top: 18px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DescriptionWrapper = styled.div`
|
|
||||||
flex: 1;
|
|
||||||
font-size: 17px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DescriptionTitle = styled.div`
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 17px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DescriptionList = styled.div`
|
|
||||||
margin-top: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DescriptionItem = styled.div`
|
|
||||||
margin-top: 12px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QuickLinksWrapper = styled.div`
|
|
||||||
margin-left: 83px;
|
|
||||||
color: #716e6e;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QuickLinksTitle = styled.div`
|
|
||||||
font-size: 14px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QuickLinksItem = styled.div`
|
|
||||||
font-size: 17px;
|
|
||||||
border-bottom: 1px solid #716e6e;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 13px;
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-left: 3px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledButton = styled.button`
|
|
||||||
color: white;
|
|
||||||
background-color: #f3672a;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 17px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-top: 30px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CompletionDialog = () => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const orgId = useSelector(getCurrentOrgId);
|
|
||||||
const currentApplication = useSelector(getCurrentApplication);
|
|
||||||
const pageId = useSelector(getCurrentPageId);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const params = getQueryParams();
|
|
||||||
const showCompletionDialog = async () => {
|
|
||||||
const inOnboarding = await getOnboardingState();
|
|
||||||
if (params.onboardingComplete && inOnboarding) {
|
|
||||||
setIsOpen(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
showCompletionDialog();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
dispatch(endOnboarding());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledDialog
|
|
||||||
isOpen={isOpen}
|
|
||||||
canOutsideClickClose={true}
|
|
||||||
canEscapeKeyClose={true}
|
|
||||||
onClose={onClose}
|
|
||||||
>
|
|
||||||
<ApplicationPublishedWrapper>
|
|
||||||
<Title>
|
|
||||||
<span role="img" aria-label="raising hands">
|
|
||||||
🙌
|
|
||||||
</span>{" "}
|
|
||||||
You’re Awesome!
|
|
||||||
</Title>
|
|
||||||
<ContentWrapper>
|
|
||||||
<DescriptionWrapper>
|
|
||||||
<DescriptionTitle>
|
|
||||||
You’ve completed this tutorial. Here’s a quick recap of things you
|
|
||||||
learnt -
|
|
||||||
</DescriptionTitle>
|
|
||||||
<DescriptionList>
|
|
||||||
<DescriptionItem>
|
|
||||||
<span role="img" aria-label="pointing right">
|
|
||||||
👉
|
|
||||||
</span>{" "}
|
|
||||||
Querying a database
|
|
||||||
</DescriptionItem>
|
|
||||||
<DescriptionItem>
|
|
||||||
<span role="img" aria-label="pointing right">
|
|
||||||
👉
|
|
||||||
</span>{" "}
|
|
||||||
Building UI using widgets.
|
|
||||||
</DescriptionItem>
|
|
||||||
<DescriptionItem>
|
|
||||||
<span role="img" aria-label="pointing right">
|
|
||||||
👉
|
|
||||||
</span>{" "}
|
|
||||||
Connecting widgets to queries using {"{{}}"} bindings
|
|
||||||
</DescriptionItem>
|
|
||||||
<DescriptionItem>
|
|
||||||
<span role="img" aria-label="pointing right">
|
|
||||||
👉
|
|
||||||
</span>{" "}
|
|
||||||
Deploying your application
|
|
||||||
</DescriptionItem>
|
|
||||||
</DescriptionList>
|
|
||||||
|
|
||||||
<StyledButton className="t--continue-on-my-own" onClick={onClose}>
|
|
||||||
Continue on my own
|
|
||||||
</StyledButton>
|
|
||||||
</DescriptionWrapper>
|
|
||||||
<QuickLinksWrapper>
|
|
||||||
<QuickLinksTitle>Quick Links:</QuickLinksTitle>
|
|
||||||
<QuickLinksItem
|
|
||||||
onClick={() =>
|
|
||||||
window.open("https://docs.appsmith.com/", "_blank")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<IconWrapper color="#716E6E" width={14} height={17}>
|
|
||||||
<BookIcon />
|
|
||||||
</IconWrapper>
|
|
||||||
<span className="text">Read our documentation</span>
|
|
||||||
</QuickLinksItem>
|
|
||||||
<FormDialogComponent
|
|
||||||
trigger={
|
|
||||||
<QuickLinksItem>
|
|
||||||
<HeaderIcons.SHARE color={"#716E6E"} width={14} height={17} />
|
|
||||||
<span className="text">Invite users to your app</span>
|
|
||||||
</QuickLinksItem>
|
|
||||||
}
|
|
||||||
canOutsideClickClose={true}
|
|
||||||
Form={AppInviteUsersForm}
|
|
||||||
orgId={orgId}
|
|
||||||
applicationId={currentApplication?.id}
|
|
||||||
title={
|
|
||||||
currentApplication
|
|
||||||
? currentApplication.name
|
|
||||||
: "Share Application"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<QuickLinksItem
|
|
||||||
onClick={() => {
|
|
||||||
window.open(
|
|
||||||
DATA_SOURCES_EDITOR_URL(currentApplication?.id, pageId),
|
|
||||||
"_blank",
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon="plus" color="#716E6E" iconSize={15} />
|
|
||||||
<span className="text">Connect your database</span>
|
|
||||||
</QuickLinksItem>
|
|
||||||
</QuickLinksWrapper>
|
|
||||||
</ContentWrapper>
|
|
||||||
</ApplicationPublishedWrapper>
|
|
||||||
</StyledDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CompletionDialog;
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
import React, {
|
|
||||||
MutableRefObject,
|
|
||||||
ReactNode,
|
|
||||||
RefObject,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { Classes, Icon, Popover, Position } from "@blueprintjs/core";
|
|
||||||
import { useSelector } from "store";
|
|
||||||
import { getTooltipConfig } from "sagas/OnboardingSagas";
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import useClipboard from "utils/hooks/useClipboard";
|
|
||||||
import { endOnboarding, showTooltip } from "actions/onboardingActions";
|
|
||||||
import { Colors } from "constants/Colors";
|
|
||||||
import {
|
|
||||||
OnboardingStep,
|
|
||||||
OnboardingTooltip,
|
|
||||||
} from "constants/OnboardingConstants";
|
|
||||||
|
|
||||||
enum TooltipClassNames {
|
|
||||||
TITLE = "tooltip-title",
|
|
||||||
DESCRIPTION = "tooltip-description",
|
|
||||||
SKIP = "tooltip-skip",
|
|
||||||
ACTION = "tooltip-action",
|
|
||||||
SNIPPET = "tooltip-snippet",
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = styled.div<{ isFinalStep: boolean }>`
|
|
||||||
width: 280px;
|
|
||||||
background-color: ${props => (props.isFinalStep ? "#F86A2B" : "#457ae6")};
|
|
||||||
color: white;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
.${TooltipClassNames.TITLE} {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.${TooltipClassNames.DESCRIPTION} {
|
|
||||||
font-size: 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
.${TooltipClassNames.SKIP} {
|
|
||||||
font-size: 10px;
|
|
||||||
opacity: 0.7;
|
|
||||||
|
|
||||||
span {
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.${TooltipClassNames.SNIPPET} {
|
|
||||||
background-color: #2c59b4;
|
|
||||||
color: white;
|
|
||||||
font-size: 12px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin: 8px 0px;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
& > span {
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& div.clipboard-message {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
&.success {
|
|
||||||
background: #2c59b4;
|
|
||||||
}
|
|
||||||
&.error {
|
|
||||||
background: ${Colors.RED};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.${Classes.ICON} {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.${TooltipClassNames.ACTION} {
|
|
||||||
padding: 6px 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
font-size: 12px;
|
|
||||||
background-color: #2c59b4;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Container = styled.div<{ isFinalStep: boolean }>`
|
|
||||||
div.${Classes.POPOVER_ARROW} {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.bp3-popover-arrow-fill {
|
|
||||||
fill: ${props => (props.isFinalStep ? "#F86A2B" : "#457ae6")};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ActionWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type OnboardingToolTipProps = {
|
|
||||||
step: OnboardingStep[];
|
|
||||||
children: ReactNode;
|
|
||||||
show?: boolean;
|
|
||||||
position?: Position;
|
|
||||||
};
|
|
||||||
|
|
||||||
const OnboardingToolTip: React.FC<OnboardingToolTipProps> = (
|
|
||||||
props: OnboardingToolTipProps,
|
|
||||||
) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const showingTooltip = useSelector(
|
|
||||||
state => state.ui.onBoarding.showingTooltip,
|
|
||||||
);
|
|
||||||
const popoverRef: RefObject<Popover> = useRef(null);
|
|
||||||
const tooltipConfig = useSelector(getTooltipConfig);
|
|
||||||
const { isFinalStep = false } = tooltipConfig;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.step.includes(showingTooltip) && props.show) {
|
|
||||||
setIsOpen(true);
|
|
||||||
} else {
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
if (popoverRef.current) {
|
|
||||||
popoverRef.current.reposition();
|
|
||||||
}
|
|
||||||
}, [props.step, props.show, showingTooltip, popoverRef]);
|
|
||||||
|
|
||||||
if (isOpen) {
|
|
||||||
return (
|
|
||||||
<Container className="t--onboarding-tooltip" isFinalStep={isFinalStep}>
|
|
||||||
<Popover
|
|
||||||
ref={popoverRef}
|
|
||||||
isOpen={true}
|
|
||||||
autoFocus={false}
|
|
||||||
enforceFocus={false}
|
|
||||||
boundary={"viewport"}
|
|
||||||
usePortal={false}
|
|
||||||
position={props.position || Position.TOP}
|
|
||||||
modifiers={{
|
|
||||||
preventOverflow: { enabled: false },
|
|
||||||
hide: { enabled: false },
|
|
||||||
flip: { enabled: false },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
<ToolTipContent details={tooltipConfig} />
|
|
||||||
</Popover>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>{props.children}</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
OnboardingToolTip.defaultProps = {
|
|
||||||
show: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
type ToolTipContentProps = {
|
|
||||||
details: OnboardingTooltip;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ToolTipContent = (props: ToolTipContentProps) => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
snippet,
|
|
||||||
action,
|
|
||||||
isFinalStep = false,
|
|
||||||
} = props.details;
|
|
||||||
const snippetRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
|
|
||||||
const write = useClipboard(snippetRef);
|
|
||||||
|
|
||||||
const copyBindingToClipboard = () => {
|
|
||||||
snippet && write(snippet);
|
|
||||||
};
|
|
||||||
|
|
||||||
const finishOnboarding = () => {
|
|
||||||
dispatch(endOnboarding());
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper isFinalStep={isFinalStep}>
|
|
||||||
<div className={TooltipClassNames.TITLE}>{title}</div>
|
|
||||||
<div className={TooltipClassNames.DESCRIPTION}>{description}</div>
|
|
||||||
|
|
||||||
{snippet && (
|
|
||||||
<div
|
|
||||||
className={TooltipClassNames.SNIPPET}
|
|
||||||
onClick={copyBindingToClipboard}
|
|
||||||
ref={snippetRef}
|
|
||||||
>
|
|
||||||
<span>{snippet}</span>
|
|
||||||
<Icon icon="duplicate" iconSize={14} color={Colors.WHITE} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ActionWrapper>
|
|
||||||
<span className={TooltipClassNames.SKIP}>
|
|
||||||
Done? <span onClick={finishOnboarding}>Click here to End</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{action && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
if (action.action) {
|
|
||||||
dispatch(action.action);
|
|
||||||
dispatch(showTooltip(action.action.payload));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(showTooltip(OnboardingStep.NONE));
|
|
||||||
}}
|
|
||||||
className={TooltipClassNames.ACTION}
|
|
||||||
>
|
|
||||||
{action.label}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</ActionWrapper>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OnboardingToolTip;
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
import { setCurrentStep } from "actions/onboardingActions";
|
|
||||||
import { ReduxAction, ReduxActionTypes } from "./ReduxActionConstants";
|
|
||||||
import { EventName } from "../utils/AnalyticsUtil";
|
|
||||||
|
|
||||||
export enum OnboardingStep {
|
|
||||||
NONE = -1,
|
|
||||||
WELCOME = 0,
|
|
||||||
EXAMPLE_DATABASE = 1,
|
|
||||||
ADD_WIDGET = 2,
|
|
||||||
SUCCESSFUL_BINDING = 3,
|
|
||||||
DEPLOY = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OnboardingTooltip = {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
action?: {
|
|
||||||
label: string;
|
|
||||||
action?: ReduxAction<OnboardingStep>;
|
|
||||||
};
|
|
||||||
snippet?: string;
|
|
||||||
isFinalStep?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OnboardingStepConfig = {
|
|
||||||
setup: () => { type: string; payload?: any }[];
|
|
||||||
tooltip: OnboardingTooltip;
|
|
||||||
eventName?: EventName;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OnboardingConfig: Record<OnboardingStep, OnboardingStepConfig> = {
|
|
||||||
[OnboardingStep.NONE]: {
|
|
||||||
setup: () => {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[OnboardingStep.WELCOME]: {
|
|
||||||
setup: () => {
|
|
||||||
// To setup the state if any
|
|
||||||
// Return action that needs to be dispatched
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: ReduxActionTypes.SHOW_WELCOME,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
eventName: "ONBOARDING_WELCOME",
|
|
||||||
},
|
|
||||||
[OnboardingStep.EXAMPLE_DATABASE]: {
|
|
||||||
setup: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: ReduxActionTypes.CREATE_ONBOARDING_DBQUERY_INIT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ReduxActionTypes.LISTEN_FOR_ADD_WIDGET,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: ReduxActionTypes.LISTEN_FOR_TABLE_WIDGET_BINDING,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
title: "Say hello to your example database",
|
|
||||||
description:
|
|
||||||
"Go ahead, check it out. You can also create a new query or connect to your own db as well.",
|
|
||||||
action: {
|
|
||||||
label: "Got It!",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
eventName: "ONBOARDING_EXAMPLE_DATABASE",
|
|
||||||
},
|
|
||||||
[OnboardingStep.ADD_WIDGET]: {
|
|
||||||
setup: () => {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
title:
|
|
||||||
"Wohoo! Your first widget. 🎉 Go ahead and connect this to a Query",
|
|
||||||
description:
|
|
||||||
"Copy the example binding below and paste inside TableData input",
|
|
||||||
snippet: "{{ExampleQuery.data}}",
|
|
||||||
},
|
|
||||||
eventName: "ONBOARDING_ADD_WIDGET",
|
|
||||||
},
|
|
||||||
[OnboardingStep.SUCCESSFUL_BINDING]: {
|
|
||||||
setup: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: ReduxActionTypes.LISTEN_FOR_WIDGET_UNSELECTION,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
title: "This table is now connected to Example Query",
|
|
||||||
description:
|
|
||||||
"You can connect properties to variables on Appsmith with {{ }} bindings",
|
|
||||||
action: {
|
|
||||||
label: "Next",
|
|
||||||
action: setCurrentStep(OnboardingStep.DEPLOY),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
eventName: "ONBOARDING_SUCCESSFUL_BINDING",
|
|
||||||
},
|
|
||||||
[OnboardingStep.DEPLOY]: {
|
|
||||||
setup: () => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: ReduxActionTypes.LISTEN_FOR_DEPLOY,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
title: "You’re almost done! Just Hit Deploy",
|
|
||||||
description:
|
|
||||||
"Deploying your apps is a crucial step to building on appsmith.",
|
|
||||||
isFinalStep: true,
|
|
||||||
},
|
|
||||||
eventName: "ONBOARDING_DEPLOY",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -72,15 +72,6 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
CANCEL_RUN_ACTION_CONFIRM_MODAL: "CANCEL_RUN_ACTION_CONFIRM_MODAL",
|
CANCEL_RUN_ACTION_CONFIRM_MODAL: "CANCEL_RUN_ACTION_CONFIRM_MODAL",
|
||||||
ACCEPT_RUN_ACTION_CONFIRM_MODAL: "ACCEPT_RUN_ACTION_CONFIRM_MODAL",
|
ACCEPT_RUN_ACTION_CONFIRM_MODAL: "ACCEPT_RUN_ACTION_CONFIRM_MODAL",
|
||||||
CREATE_QUERY_INIT: "CREATE_QUERY_INIT",
|
CREATE_QUERY_INIT: "CREATE_QUERY_INIT",
|
||||||
CREATE_ONBOARDING_ACTION_INIT: "CREATE_ONBOARDING_ACTION_INIT",
|
|
||||||
CREATE_ONBOARDING_ACTION_SUCCESS: "CREATE_ONBOARDING_ACTION_SUCCESS",
|
|
||||||
CREATE_ONBOARDING_DBQUERY_SUCCESS: "CREATE_ONBOARDING_DBQUERY_SUCCESS",
|
|
||||||
END_ONBOARDING: "END_ONBOARDING",
|
|
||||||
SET_CURRENT_STEP: "SET_CURRENT_STEP",
|
|
||||||
SET_ONBOARDING_STATE: "SET_ONBOARDING_STATE",
|
|
||||||
NEXT_ONBOARDING_STEP: "NEXT_ONBOARDING_STEP",
|
|
||||||
INCREMENT_STEP: "INCREMENT_STEP",
|
|
||||||
SHOW_WELCOME: "SHOW_WELCOME",
|
|
||||||
FETCH_DATASOURCES_INIT: "FETCH_DATASOURCES_INIT",
|
FETCH_DATASOURCES_INIT: "FETCH_DATASOURCES_INIT",
|
||||||
FETCH_DATASOURCES_SUCCESS: "FETCH_DATASOURCES_SUCCESS",
|
FETCH_DATASOURCES_SUCCESS: "FETCH_DATASOURCES_SUCCESS",
|
||||||
SAVE_DATASOURCE_NAME: "SAVE_DATASOURCE_NAME",
|
SAVE_DATASOURCE_NAME: "SAVE_DATASOURCE_NAME",
|
||||||
|
|
@ -101,14 +92,6 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
TEST_DATASOURCE_SUCCESS: "TEST_DATASOURCE_SUCCESS",
|
TEST_DATASOURCE_SUCCESS: "TEST_DATASOURCE_SUCCESS",
|
||||||
DELETE_DATASOURCE_DRAFT: "DELETE_DATASOURCE_DRAFT",
|
DELETE_DATASOURCE_DRAFT: "DELETE_DATASOURCE_DRAFT",
|
||||||
UPDATE_DATASOURCE_DRAFT: "UPDATE_DATASOURCE_DRAFT",
|
UPDATE_DATASOURCE_DRAFT: "UPDATE_DATASOURCE_DRAFT",
|
||||||
SHOW_ONBOARDING_TOOLTIP: "SHOW_ONBOARDING_TOOLTIP",
|
|
||||||
SHOW_ONBOARDING_COMPLETION_DIALOG: "SHOW_ONBOARDING_COMPLETION_DIALOG",
|
|
||||||
CREATE_ONBOARDING_DBQUERY_INIT: "CREATE_ONBOARDING_DBQUERY_INIT",
|
|
||||||
ADD_WIDGET_COMPLETE: "ADD_WIDGET_COMPLETE",
|
|
||||||
LISTEN_FOR_ADD_WIDGET: "LISTEN_FOR_ADD_WIDGET",
|
|
||||||
LISTEN_FOR_WIDGET_UNSELECTION: "LISTEN_FOR_WIDGET_UNSELECTION",
|
|
||||||
LISTEN_FOR_DEPLOY: "LISTEN_FOR_DEPLOY",
|
|
||||||
LISTEN_FOR_TABLE_WIDGET_BINDING: "LISTEN_FOR_TABLE_WIDGET_BINDING",
|
|
||||||
FETCH_PUBLISHED_PAGE_INIT: "FETCH_PUBLISHED_PAGE_INIT",
|
FETCH_PUBLISHED_PAGE_INIT: "FETCH_PUBLISHED_PAGE_INIT",
|
||||||
FETCH_PUBLISHED_PAGE_SUCCESS: "FETCH_PUBLISHED_PAGE_SUCCESS",
|
FETCH_PUBLISHED_PAGE_SUCCESS: "FETCH_PUBLISHED_PAGE_SUCCESS",
|
||||||
DELETE_DATASOURCE_INIT: "DELETE_DATASOURCE_INIT",
|
DELETE_DATASOURCE_INIT: "DELETE_DATASOURCE_INIT",
|
||||||
|
|
@ -340,7 +323,6 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
||||||
DELETE_ACTION_ERROR: "DELETE_ACTION_ERROR",
|
DELETE_ACTION_ERROR: "DELETE_ACTION_ERROR",
|
||||||
RUN_ACTION_ERROR: "RUN_ACTION_ERROR",
|
RUN_ACTION_ERROR: "RUN_ACTION_ERROR",
|
||||||
EXECUTE_ACTION_ERROR: "EXECUTE_ACTION_ERROR",
|
EXECUTE_ACTION_ERROR: "EXECUTE_ACTION_ERROR",
|
||||||
CREATE_ONBOARDING_ACTION_ERROR: "CREATE_ONBOARDING_ACTION_ERROR",
|
|
||||||
FETCH_DATASOURCES_ERROR: "FETCH_DATASOURCES_ERROR",
|
FETCH_DATASOURCES_ERROR: "FETCH_DATASOURCES_ERROR",
|
||||||
SEARCH_APIORPROVIDERS_ERROR: "SEARCH_APIORPROVIDERS_ERROR",
|
SEARCH_APIORPROVIDERS_ERROR: "SEARCH_APIORPROVIDERS_ERROR",
|
||||||
UPDATE_DATASOURCE_ERROR: "UPDATE_DATASOURCE_ERROR",
|
UPDATE_DATASOURCE_ERROR: "UPDATE_DATASOURCE_ERROR",
|
||||||
|
|
@ -349,7 +331,6 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
||||||
DELETE_DATASOURCE_ERROR: "DELETE_DATASOURCE_ERROR",
|
DELETE_DATASOURCE_ERROR: "DELETE_DATASOURCE_ERROR",
|
||||||
FETCH_DATASOURCE_STRUCTURE_ERROR: "FETCH_DATASOURCE_STRUCTURE_ERROR",
|
FETCH_DATASOURCE_STRUCTURE_ERROR: "FETCH_DATASOURCE_STRUCTURE_ERROR",
|
||||||
REFRESH_DATASOURCE_STRUCTURE_ERROR: "REFRESH_DATASOURCE_STRUCTURE_ERROR",
|
REFRESH_DATASOURCE_STRUCTURE_ERROR: "REFRESH_DATASOURCE_STRUCTURE_ERROR",
|
||||||
CREATE_ONBOARDING_DBQUERY_ERROR: "CREATE_ONBOARDING_DBQUERY_ERROR",
|
|
||||||
FETCH_PUBLISHED_PAGE_ERROR: "FETCH_PUBLISHED_PAGE_ERROR",
|
FETCH_PUBLISHED_PAGE_ERROR: "FETCH_PUBLISHED_PAGE_ERROR",
|
||||||
PUBLISH_APPLICATION_ERROR: "PUBLISH_APPLICATION_ERROR",
|
PUBLISH_APPLICATION_ERROR: "PUBLISH_APPLICATION_ERROR",
|
||||||
FETCH_USER_DETAILS_ERROR: "FETCH_USER_DETAILS_ERROR",
|
FETCH_USER_DETAILS_ERROR: "FETCH_USER_DETAILS_ERROR",
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import {
|
||||||
getCanvasWidgetDsl,
|
getCanvasWidgetDsl,
|
||||||
getCurrentPageName,
|
getCurrentPageName,
|
||||||
} from "selectors/editorSelectors";
|
} from "selectors/editorSelectors";
|
||||||
import OnboardingCompletionDialog from "components/editorComponents/Onboarding/CompletionDialog";
|
|
||||||
import ConfirmRunModal from "pages/Editor/ConfirmRunModal";
|
import ConfirmRunModal from "pages/Editor/ConfirmRunModal";
|
||||||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
import { getCurrentApplication } from "selectors/applicationSelectors";
|
||||||
import {
|
import {
|
||||||
|
|
@ -115,7 +114,6 @@ class AppViewerPageContainer extends Component<AppViewerPageContainerProps> {
|
||||||
pageName={this.props.currentPageName}
|
pageName={this.props.currentPageName}
|
||||||
/>
|
/>
|
||||||
<ConfirmRunModal />
|
<ConfirmRunModal />
|
||||||
<OnboardingCompletionDialog />
|
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { convertArrayToSentence } from "utils/helpers";
|
import { convertArrayToSentence } from "utils/helpers";
|
||||||
import BackButton from "./BackButton";
|
import BackButton from "./BackButton";
|
||||||
import { PluginType } from "entities/Action";
|
import { PluginType } from "entities/Action";
|
||||||
import Boxed from "components/editorComponents/Onboarding/Boxed";
|
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
|
||||||
|
|
||||||
const { cloudHosting } = getAppsmithConfigs();
|
const { cloudHosting } = getAppsmithConfigs();
|
||||||
|
|
||||||
|
|
@ -62,7 +60,7 @@ const DBForm = styled.div`
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
height: calc(100vh - ${props => props.theme.headerHeight});
|
max-height: 93vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
.backBtn {
|
.backBtn {
|
||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
|
|
@ -339,22 +337,20 @@ class DatasourceDBEditor extends React.Component<
|
||||||
<FormTitle focusOnMount={this.props.isNewDatasource} />
|
<FormTitle focusOnMount={this.props.isNewDatasource} />
|
||||||
</FormTitleContainer>
|
</FormTitleContainer>
|
||||||
{viewMode && (
|
{viewMode && (
|
||||||
<Boxed step={OnboardingStep.SUCCESSFUL_BINDING}>
|
<ActionButton
|
||||||
<ActionButton
|
className="t--edit-datasource"
|
||||||
className="t--edit-datasource"
|
text="EDIT"
|
||||||
text="EDIT"
|
accent="secondary"
|
||||||
accent="secondary"
|
onClick={() => {
|
||||||
onClick={() => {
|
this.props.setDatasourceEditorMode(
|
||||||
this.props.setDatasourceEditorMode(
|
this.props.datasourceId,
|
||||||
this.props.datasourceId,
|
false,
|
||||||
false,
|
);
|
||||||
);
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
</Boxed>
|
|
||||||
)}
|
)}
|
||||||
</Header>
|
</Header>
|
||||||
{cloudHosting && pluginType === PluginType.DB && !viewMode && (
|
{cloudHosting && pluginType === PluginType.DB && (
|
||||||
<CollapsibleWrapper>
|
<CollapsibleWrapper>
|
||||||
<CollapsibleHelp>
|
<CollapsibleHelp>
|
||||||
<span>{`Whitelist the IP ${convertArrayToSentence(
|
<span>{`Whitelist the IP ${convertArrayToSentence(
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import { getDataTree } from "selectors/dataTreeSelectors";
|
||||||
import { isNameValid } from "utils/helpers";
|
import { isNameValid } from "utils/helpers";
|
||||||
import { saveDatasourceName } from "actions/datasourceActions";
|
import { saveDatasourceName } from "actions/datasourceActions";
|
||||||
import { Spinner } from "@blueprintjs/core";
|
import { Spinner } from "@blueprintjs/core";
|
||||||
import { getCurrentStep, inOnboarding } from "sagas/OnboardingSagas";
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
|
@ -53,14 +52,6 @@ const FormTitle = (props: FormTitleProps) => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// For onboarding
|
|
||||||
const hideEditIcon = useSelector((state: AppState) => {
|
|
||||||
const currentStep = getCurrentStep(state);
|
|
||||||
const isInOnboarding = inOnboarding(state);
|
|
||||||
|
|
||||||
return isInOnboarding && currentStep < 3;
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasNameConflict = React.useCallback(
|
const hasNameConflict = React.useCallback(
|
||||||
(name: string) => {
|
(name: string) => {
|
||||||
const datasourcesNames: Record<string, any> = {};
|
const datasourcesNames: Record<string, any> = {};
|
||||||
|
|
@ -113,14 +104,13 @@ const FormTitle = (props: FormTitleProps) => {
|
||||||
<EditableText
|
<EditableText
|
||||||
className="t--edit-datasource-name"
|
className="t--edit-datasource-name"
|
||||||
type="text"
|
type="text"
|
||||||
hideEditIcon={hideEditIcon}
|
|
||||||
forceDefault={forceUpdate}
|
forceDefault={forceUpdate}
|
||||||
defaultValue={currentDatasource ? currentDatasource.name : ""}
|
defaultValue={currentDatasource ? currentDatasource.name : ""}
|
||||||
isInvalid={isInvalidDatasourceName}
|
isInvalid={isInvalidDatasourceName}
|
||||||
onTextChanged={handleDatasourceNameChange}
|
onTextChanged={handleDatasourceNameChange}
|
||||||
placeholder="Datasource Name"
|
placeholder="Datasource Name"
|
||||||
editInteractionKind={EditInteractionKind.SINGLE}
|
editInteractionKind={EditInteractionKind.SINGLE}
|
||||||
isEditingDefault={props.focusOnMount && !hideEditIcon}
|
isEditingDefault={props.focusOnMount}
|
||||||
updating={saveStatus.isSaving}
|
updating={saveStatus.isSaving}
|
||||||
/>
|
/>
|
||||||
{saveStatus.isSaving && <Spinner size={16} />}
|
{saveStatus.isSaving && <Spinner size={16} />}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
||||||
const formData = getFormValues(DATASOURCE_DB_FORM)(state) as Datasource;
|
const formData = getFormValues(DATASOURCE_DB_FORM)(state) as Datasource;
|
||||||
const pluginId = _.get(datasource, "pluginId", "");
|
const pluginId = _.get(datasource, "pluginId", "");
|
||||||
const plugin = getPlugin(state, pluginId);
|
const plugin = getPlugin(state, pluginId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pluginImages: getPluginImages(state),
|
pluginImages: getPluginImages(state),
|
||||||
formData,
|
formData,
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,6 @@ import {
|
||||||
getIsSavingAppName,
|
getIsSavingAppName,
|
||||||
} from "selectors/applicationSelectors";
|
} from "selectors/applicationSelectors";
|
||||||
import EditableTextWrapper from "components/ads/EditableTextWrapper";
|
import EditableTextWrapper from "components/ads/EditableTextWrapper";
|
||||||
import Boxed from "components/editorComponents/Onboarding/Boxed";
|
|
||||||
import OnboardingToolTip from "components/editorComponents/Onboarding/Tooltip";
|
|
||||||
import { Position } from "@blueprintjs/core";
|
|
||||||
import { inOnboarding } from "sagas/OnboardingSagas";
|
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
|
||||||
|
|
||||||
const HeaderWrapper = styled(StyledHeader)`
|
const HeaderWrapper = styled(StyledHeader)`
|
||||||
background: ${Colors.BALTIC_SEA};
|
background: ${Colors.BALTIC_SEA};
|
||||||
|
|
@ -139,7 +134,6 @@ type EditorHeaderProps = {
|
||||||
applicationId?: string;
|
applicationId?: string;
|
||||||
currentApplication?: ApplicationPayload;
|
currentApplication?: ApplicationPayload;
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
isInOnboarding: boolean;
|
|
||||||
publishApplication: (appId: string) => void;
|
publishApplication: (appId: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -154,7 +148,6 @@ export const EditorHeader = (props: EditorHeaderProps) => {
|
||||||
applicationId,
|
applicationId,
|
||||||
pageName,
|
pageName,
|
||||||
publishApplication,
|
publishApplication,
|
||||||
isInOnboarding,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
@ -216,109 +209,93 @@ export const EditorHeader = (props: EditorHeaderProps) => {
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</HeaderSection>
|
</HeaderSection>
|
||||||
<Boxed step={OnboardingStep.DEPLOY}>
|
<HeaderSection flex-direction={"row"}>
|
||||||
<HeaderSection flex-direction={"row"}>
|
{currentApplication ? (
|
||||||
{currentApplication ? (
|
<EditableTextWrapper
|
||||||
<EditableTextWrapper
|
variant="UNDERLINE"
|
||||||
variant="UNDERLINE"
|
defaultValue={currentApplication.name || ""}
|
||||||
defaultValue={currentApplication.name || ""}
|
editInteractionKind={EditInteractionKind.SINGLE}
|
||||||
editInteractionKind={EditInteractionKind.SINGLE}
|
hideEditIcon={true}
|
||||||
hideEditIcon={true}
|
className="t--application-name"
|
||||||
className="t--application-name"
|
fill={false}
|
||||||
fill={false}
|
savingState={
|
||||||
savingState={
|
isSavingName ? SavingState.STARTED : SavingState.NOT_STARTED
|
||||||
isSavingName ? SavingState.STARTED : SavingState.NOT_STARTED
|
}
|
||||||
}
|
isNewApp={
|
||||||
isNewApp={
|
applicationList.filter(el => el.id === applicationId).length > 0
|
||||||
!isInOnboarding &&
|
}
|
||||||
applicationList.filter(el => el.id === applicationId).length > 0
|
onBlur={(value: string) =>
|
||||||
}
|
updateApplicationDispatch(applicationId || "", {
|
||||||
onBlur={(value: string) =>
|
name: value,
|
||||||
updateApplicationDispatch(applicationId || "", {
|
currentApp: true,
|
||||||
name: value,
|
})
|
||||||
currentApp: true,
|
}
|
||||||
})
|
/>
|
||||||
|
) : null}
|
||||||
|
{/* <PageName>{pageName} </PageName> */}
|
||||||
|
</HeaderSection>
|
||||||
|
<HeaderSection>
|
||||||
|
<SaveStatusContainer className={"t--save-status-container"}>
|
||||||
|
{saveStatusIcon}
|
||||||
|
</SaveStatusContainer>
|
||||||
|
<ShareButton
|
||||||
|
target="_blank"
|
||||||
|
href="https://mail.google.com/mail/u/0/?view=cm&fs=1&to=feedback@appsmith.com&tf=1"
|
||||||
|
text="Feedback"
|
||||||
|
intent="none"
|
||||||
|
outline
|
||||||
|
size="small"
|
||||||
|
className="t--application-feedback-btn"
|
||||||
|
icon={
|
||||||
|
<HeaderIcons.FEEDBACK color={Colors.WHITE} width={13} height={13} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<FormDialogComponent
|
||||||
|
trigger={
|
||||||
|
<ShareButton
|
||||||
|
text="Share"
|
||||||
|
intent="none"
|
||||||
|
outline
|
||||||
|
size="small"
|
||||||
|
className="t--application-share-btn"
|
||||||
|
icon={
|
||||||
|
<HeaderIcons.SHARE
|
||||||
|
color={Colors.WHITE}
|
||||||
|
width={13}
|
||||||
|
height={13}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : null}
|
}
|
||||||
{/* <PageName>{pageName} </PageName> */}
|
canOutsideClickClose={true}
|
||||||
</HeaderSection>
|
Form={AppInviteUsersForm}
|
||||||
<HeaderSection>
|
orgId={orgId}
|
||||||
<SaveStatusContainer className={"t--save-status-container"}>
|
applicationId={applicationId}
|
||||||
{saveStatusIcon}
|
title={
|
||||||
</SaveStatusContainer>
|
currentApplication ? currentApplication.name : "Share Application"
|
||||||
<ShareButton
|
}
|
||||||
target="_blank"
|
/>
|
||||||
href="https://mail.google.com/mail/u/0/?view=cm&fs=1&to=feedback@appsmith.com&tf=1"
|
<DeploySection>
|
||||||
text="Feedback"
|
<DeployButton
|
||||||
intent="none"
|
onClick={handlePublish}
|
||||||
outline
|
text="Deploy"
|
||||||
|
loading={isPublishing}
|
||||||
|
intent="primary"
|
||||||
|
filled
|
||||||
size="small"
|
size="small"
|
||||||
className="t--application-feedback-btn"
|
className="t--application-publish-btn"
|
||||||
icon={
|
icon={
|
||||||
<HeaderIcons.FEEDBACK
|
<HeaderIcons.DEPLOY color={Colors.WHITE} width={13} height={13} />
|
||||||
color={Colors.WHITE}
|
|
||||||
width={13}
|
|
||||||
height={13}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<FormDialogComponent
|
<DeployLinkButtonDialog
|
||||||
trigger={
|
trigger={
|
||||||
<ShareButton
|
<DeployLinkButton icon="caret-down" filled intent="primary" />
|
||||||
text="Share"
|
|
||||||
intent="none"
|
|
||||||
outline
|
|
||||||
size="small"
|
|
||||||
className="t--application-share-btn"
|
|
||||||
icon={
|
|
||||||
<HeaderIcons.SHARE
|
|
||||||
color={Colors.WHITE}
|
|
||||||
width={13}
|
|
||||||
height={13}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
canOutsideClickClose={true}
|
|
||||||
Form={AppInviteUsersForm}
|
|
||||||
orgId={orgId}
|
|
||||||
applicationId={applicationId}
|
|
||||||
title={
|
|
||||||
currentApplication ? currentApplication.name : "Share Application"
|
|
||||||
}
|
}
|
||||||
|
link={getApplicationViewerPageURL(applicationId, pageId)}
|
||||||
/>
|
/>
|
||||||
<DeploySection>
|
</DeploySection>
|
||||||
<OnboardingToolTip
|
</HeaderSection>
|
||||||
step={[OnboardingStep.DEPLOY]}
|
|
||||||
position={Position.BOTTOM_RIGHT}
|
|
||||||
>
|
|
||||||
<DeployButton
|
|
||||||
onClick={handlePublish}
|
|
||||||
text="Deploy"
|
|
||||||
loading={isPublishing}
|
|
||||||
intent="primary"
|
|
||||||
filled
|
|
||||||
size="small"
|
|
||||||
className="t--application-publish-btn"
|
|
||||||
icon={
|
|
||||||
<HeaderIcons.DEPLOY
|
|
||||||
color={Colors.WHITE}
|
|
||||||
width={13}
|
|
||||||
height={13}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</OnboardingToolTip>
|
|
||||||
<DeployLinkButtonDialog
|
|
||||||
trigger={
|
|
||||||
<DeployLinkButton icon="caret-down" filled intent="primary" />
|
|
||||||
}
|
|
||||||
link={getApplicationViewerPageURL(applicationId, pageId)}
|
|
||||||
/>
|
|
||||||
</DeploySection>
|
|
||||||
</HeaderSection>
|
|
||||||
</Boxed>
|
|
||||||
<HelpModal page={"Editor"} />
|
<HelpModal page={"Editor"} />
|
||||||
</HeaderWrapper>
|
</HeaderWrapper>
|
||||||
);
|
);
|
||||||
|
|
@ -332,7 +309,6 @@ const mapStateToProps = (state: AppState) => ({
|
||||||
currentApplication: state.ui.applications.currentApplication,
|
currentApplication: state.ui.applications.currentApplication,
|
||||||
isPublishing: getIsPublishingApplication(state),
|
isPublishing: getIsPublishingApplication(state),
|
||||||
pageId: getCurrentPageId(state),
|
pageId: getCurrentPageId(state),
|
||||||
isInOnboarding: inOnboarding(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any) => ({
|
const mapDispatchToProps = (dispatch: any) => ({
|
||||||
|
|
|
||||||
|
|
@ -97,9 +97,8 @@ export const getPluginGroups = (
|
||||||
datasources: Datasource[],
|
datasources: Datasource[],
|
||||||
plugins: Plugin[],
|
plugins: Plugin[],
|
||||||
searchKeyword?: string,
|
searchKeyword?: string,
|
||||||
actionPluginMap = ACTION_PLUGIN_MAP,
|
|
||||||
) => {
|
) => {
|
||||||
return actionPluginMap?.map((config?: ActionGroupConfig) => {
|
return ACTION_PLUGIN_MAP?.map((config?: ActionGroupConfig) => {
|
||||||
if (!config) return null;
|
if (!config) return null;
|
||||||
|
|
||||||
const entries = actions?.filter(
|
const entries = actions?.filter(
|
||||||
|
|
@ -109,7 +108,6 @@ export const getPluginGroups = (
|
||||||
const filteredPlugins = plugins.filter(
|
const filteredPlugins = plugins.filter(
|
||||||
plugin => plugin.type === config.type,
|
plugin => plugin.type === config.type,
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredPluginIds = filteredPlugins.map(plugin => plugin.id);
|
const filteredPluginIds = filteredPlugins.map(plugin => plugin.id);
|
||||||
const filteredDatasources = datasources.filter(datasource => {
|
const filteredDatasources = datasources.filter(datasource => {
|
||||||
return filteredPluginIds.includes(datasource.pluginId);
|
return filteredPluginIds.includes(datasource.pluginId);
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
import React, { useRef, MutableRefObject, useCallback, useEffect } from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import Divider from "components/editorComponents/Divider";
|
|
||||||
import {
|
|
||||||
useFilteredEntities,
|
|
||||||
useWidgets,
|
|
||||||
useActions,
|
|
||||||
useFilteredDatasources,
|
|
||||||
} from "./hooks";
|
|
||||||
import Search from "./ExplorerSearch";
|
|
||||||
import ExplorerPageGroup from "./Pages/PageGroup";
|
|
||||||
import { scrollbarDark } from "constants/DefaultTheme";
|
|
||||||
import { NonIdealState, Classes, IPanelProps } from "@blueprintjs/core";
|
|
||||||
import WidgetSidebar from "../WidgetSidebar";
|
|
||||||
import { BUILDER_PAGE_URL } from "constants/routes";
|
|
||||||
import history from "utils/history";
|
|
||||||
import { useParams } from "react-router";
|
|
||||||
import { ExplorerURLParams } from "./helpers";
|
|
||||||
import JSDependencies from "./JSDependencies";
|
|
||||||
import PerformanceTracker, {
|
|
||||||
PerformanceTransactionName,
|
|
||||||
} from "utils/PerformanceTracker";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { getPlugins } from "selectors/entitiesSelector";
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: scroll;
|
|
||||||
${scrollbarDark};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const NoResult = styled(NonIdealState)`
|
|
||||||
&.${Classes.NON_IDEAL_STATE} {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledDivider = styled(Divider)`
|
|
||||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const EntityExplorer = (props: IPanelProps) => {
|
|
||||||
const { applicationId } = useParams<ExplorerURLParams>();
|
|
||||||
const searchInputRef: MutableRefObject<HTMLInputElement | null> = useRef(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
PerformanceTracker.startTracking(PerformanceTransactionName.ENTITY_EXPLORER);
|
|
||||||
useEffect(() => {
|
|
||||||
PerformanceTracker.stopTracking();
|
|
||||||
});
|
|
||||||
const explorerRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const { searchKeyword, clearSearch } = useFilteredEntities(searchInputRef);
|
|
||||||
const datasources = useFilteredDatasources(searchKeyword);
|
|
||||||
|
|
||||||
const plugins = useSelector(getPlugins);
|
|
||||||
|
|
||||||
const widgets = useWidgets(searchKeyword);
|
|
||||||
const actions = useActions(searchKeyword);
|
|
||||||
|
|
||||||
let noResults = false;
|
|
||||||
if (searchKeyword) {
|
|
||||||
const noWidgets = Object.values(widgets).filter(Boolean).length === 0;
|
|
||||||
const noActions =
|
|
||||||
Object.values(actions).filter(actions => actions && actions.length > 0)
|
|
||||||
.length === 0;
|
|
||||||
const noDatasource =
|
|
||||||
Object.values(datasources).filter(
|
|
||||||
datasources => datasources && datasources.length > 0,
|
|
||||||
).length === 0;
|
|
||||||
noResults = noWidgets && noActions && noDatasource;
|
|
||||||
}
|
|
||||||
const { openPanel } = props;
|
|
||||||
const showWidgetsSidebar = useCallback(
|
|
||||||
(pageId: string) => {
|
|
||||||
history.push(BUILDER_PAGE_URL(applicationId, pageId));
|
|
||||||
openPanel({ component: WidgetSidebar });
|
|
||||||
},
|
|
||||||
[openPanel, applicationId],
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Wrapper ref={explorerRef}>
|
|
||||||
<Search ref={searchInputRef} clear={clearSearch} />
|
|
||||||
<ExplorerPageGroup
|
|
||||||
searchKeyword={searchKeyword}
|
|
||||||
step={0}
|
|
||||||
widgets={widgets}
|
|
||||||
actions={actions}
|
|
||||||
datasources={datasources}
|
|
||||||
plugins={plugins}
|
|
||||||
showWidgetsSidebar={showWidgetsSidebar}
|
|
||||||
/>
|
|
||||||
{noResults && (
|
|
||||||
<NoResult
|
|
||||||
className={Classes.DARK}
|
|
||||||
description="Try modifying the search keyword."
|
|
||||||
title="No entities found"
|
|
||||||
icon="search"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<StyledDivider />
|
|
||||||
<JSDependencies />
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
EntityExplorer.displayName = "EntityExplorer";
|
|
||||||
|
|
||||||
EntityExplorer.whyDidYouRender = {
|
|
||||||
logOnDifferentValues: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EntityExplorer;
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { PluginType } from "entities/Action";
|
|
||||||
import React from "react";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { AppState } from "reducers";
|
|
||||||
import { getPlugins } from "selectors/entitiesSelector";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import { getPluginGroups, ACTION_PLUGIN_MAP } from "../Actions/helpers";
|
|
||||||
import { useActions, useFilteredDatasources } from "../hooks";
|
|
||||||
|
|
||||||
const AddWidget = styled.button`
|
|
||||||
margin: 25px 0px;
|
|
||||||
padding: 6px 38px;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 1px solid #f3672a;
|
|
||||||
color: #f3672a;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DBQueryGroup = (props: any) => {
|
|
||||||
const pages = useSelector((state: AppState) => {
|
|
||||||
return state.entities.pageList.pages;
|
|
||||||
});
|
|
||||||
const currentPage = pages[0];
|
|
||||||
const actions = useActions("");
|
|
||||||
const datasources = useFilteredDatasources("");
|
|
||||||
const plugins = useSelector(getPlugins);
|
|
||||||
const dbPluginMap = ACTION_PLUGIN_MAP.filter(
|
|
||||||
plugin => plugin?.type === PluginType.DB,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Wrapper>
|
|
||||||
<AddWidget className="t--add-widget" onClick={props.showWidgetsSidebar}>
|
|
||||||
Add Widget
|
|
||||||
</AddWidget>
|
|
||||||
</Wrapper>
|
|
||||||
{getPluginGroups(
|
|
||||||
currentPage,
|
|
||||||
0,
|
|
||||||
actions[currentPage.pageId] || [],
|
|
||||||
datasources[currentPage.pageId] || [],
|
|
||||||
plugins,
|
|
||||||
"",
|
|
||||||
dbPluginMap,
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DBQueryGroup;
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import { Classes } from "@blueprintjs/core";
|
|
||||||
|
|
||||||
const SkeletonRows = styled.div<{ size: number }>`
|
|
||||||
height: 20px;
|
|
||||||
width: ${props => props.size}%;
|
|
||||||
margin-top: 12px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Loading = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SkeletonRows size={90} className={Classes.SKELETON} />
|
|
||||||
<SkeletonRows size={60} className={Classes.SKELETON} />
|
|
||||||
<SkeletonRows size={30} className={Classes.SKELETON} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Loading;
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import React, { useCallback } from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import { scrollbarDark } from "constants/DefaultTheme";
|
|
||||||
import Loading from "./Loading";
|
|
||||||
import DBQueryGroup from "./DBQueryGroup";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { AppState } from "reducers";
|
|
||||||
import { IPanelProps } from "@blueprintjs/core";
|
|
||||||
import { BUILDER_PAGE_URL } from "constants/routes";
|
|
||||||
import WidgetSidebar from "pages/Editor/WidgetSidebar";
|
|
||||||
import { useParams } from "react-router";
|
|
||||||
import { ExplorerURLParams } from "../helpers";
|
|
||||||
import history from "utils/history";
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: scroll;
|
|
||||||
${scrollbarDark};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const OnboardingExplorer = (props: IPanelProps) => {
|
|
||||||
let node = <Loading />;
|
|
||||||
const { applicationId, pageId } = useParams<ExplorerURLParams>();
|
|
||||||
const { openPanel } = props;
|
|
||||||
const showWidgetsSidebar = useCallback(() => {
|
|
||||||
history.push(BUILDER_PAGE_URL(applicationId, pageId));
|
|
||||||
openPanel({ component: WidgetSidebar });
|
|
||||||
}, [openPanel, applicationId, pageId]);
|
|
||||||
|
|
||||||
const createdDBQuery = useSelector(
|
|
||||||
(state: AppState) => state.ui.onBoarding.createdDBQuery,
|
|
||||||
);
|
|
||||||
if (createdDBQuery) {
|
|
||||||
node = <DBQueryGroup showWidgetsSidebar={showWidgetsSidebar} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Wrapper>{node}</Wrapper>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OnboardingExplorer;
|
|
||||||
|
|
@ -12,8 +12,6 @@ import ExplorerDatasourceEntity from "../Datasources/DatasourceEntity";
|
||||||
import Entity from "../Entity";
|
import Entity from "../Entity";
|
||||||
import EntityPlaceholder from "../Entity/Placeholder";
|
import EntityPlaceholder from "../Entity/Placeholder";
|
||||||
import { ExplorerURLParams } from "../helpers";
|
import { ExplorerURLParams } from "../helpers";
|
||||||
import OnboardingTooltip from "components/editorComponents/Onboarding/Tooltip";
|
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
|
||||||
|
|
||||||
type ExplorerPluginGroupProps = {
|
type ExplorerPluginGroupProps = {
|
||||||
step: number;
|
step: number;
|
||||||
|
|
@ -84,21 +82,16 @@ const ExplorerPluginGroup = memo((props: ExplorerPluginGroupProps) => {
|
||||||
config={props.actionConfig}
|
config={props.actionConfig}
|
||||||
plugins={pluginGroups}
|
plugins={pluginGroups}
|
||||||
/>
|
/>
|
||||||
{props.datasources.map((datasource: Datasource, index: number) => {
|
{props.datasources.map((datasource: Datasource) => {
|
||||||
return (
|
return (
|
||||||
<OnboardingTooltip
|
<ExplorerDatasourceEntity
|
||||||
step={[OnboardingStep.EXAMPLE_DATABASE]}
|
plugin={pluginGroups[datasource.pluginId]}
|
||||||
key={datasource.id}
|
key={datasource.id}
|
||||||
show={index === 0}
|
datasource={datasource}
|
||||||
>
|
step={props.step + 1}
|
||||||
<ExplorerDatasourceEntity
|
searchKeyword={props.searchKeyword}
|
||||||
plugin={pluginGroups[datasource.pluginId]}
|
pageId={props.page.pageId}
|
||||||
datasource={datasource}
|
/>
|
||||||
step={props.step + 1}
|
|
||||||
searchKeyword={props.searchKeyword}
|
|
||||||
pageId={props.page.pageId}
|
|
||||||
/>
|
|
||||||
</OnboardingTooltip>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,112 @@
|
||||||
import { IPanelProps } from "@blueprintjs/core";
|
import React, { useRef, MutableRefObject, useCallback, useEffect } from "react";
|
||||||
import React from "react";
|
import styled from "styled-components";
|
||||||
|
import Divider from "components/editorComponents/Divider";
|
||||||
|
import {
|
||||||
|
useFilteredEntities,
|
||||||
|
useWidgets,
|
||||||
|
useActions,
|
||||||
|
useFilteredDatasources,
|
||||||
|
} from "./hooks";
|
||||||
|
import Search from "./ExplorerSearch";
|
||||||
|
import ExplorerPageGroup from "./Pages/PageGroup";
|
||||||
|
import { scrollbarDark } from "constants/DefaultTheme";
|
||||||
|
import { NonIdealState, Classes, IPanelProps } from "@blueprintjs/core";
|
||||||
|
import WidgetSidebar from "../WidgetSidebar";
|
||||||
|
import { BUILDER_PAGE_URL } from "constants/routes";
|
||||||
|
import history from "utils/history";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import { ExplorerURLParams } from "./helpers";
|
||||||
|
import JSDependencies from "./JSDependencies";
|
||||||
|
import PerformanceTracker, {
|
||||||
|
PerformanceTransactionName,
|
||||||
|
} from "utils/PerformanceTracker";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { inOnboarding, isAddWidgetComplete } from "sagas/OnboardingSagas";
|
import { getPlugins } from "selectors/entitiesSelector";
|
||||||
import EntityExplorer from "./EntityExplorer";
|
|
||||||
import OnboardingExplorer from "./Onboarding";
|
|
||||||
|
|
||||||
const ExplorerContent = (props: IPanelProps) => {
|
const Wrapper = styled.div`
|
||||||
const isInOnboarding = useSelector(inOnboarding);
|
height: 100%;
|
||||||
const addWidgetComplete = useSelector(isAddWidgetComplete);
|
overflow-y: scroll;
|
||||||
|
${scrollbarDark};
|
||||||
|
`;
|
||||||
|
|
||||||
if (isInOnboarding && !addWidgetComplete) {
|
const NoResult = styled(NonIdealState)`
|
||||||
return <OnboardingExplorer {...props} />;
|
&.${Classes.NON_IDEAL_STATE} {
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
return <EntityExplorer {...props} />;
|
const StyledDivider = styled(Divider)`
|
||||||
|
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const EntityExplorer = (props: IPanelProps) => {
|
||||||
|
const { applicationId, pageId } = useParams<ExplorerURLParams>();
|
||||||
|
const searchInputRef: MutableRefObject<HTMLInputElement | null> = useRef(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
PerformanceTracker.startTracking(PerformanceTransactionName.ENTITY_EXPLORER);
|
||||||
|
useEffect(() => {
|
||||||
|
PerformanceTracker.stopTracking();
|
||||||
|
});
|
||||||
|
const explorerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const { searchKeyword, clearSearch } = useFilteredEntities(searchInputRef);
|
||||||
|
const datasources = useFilteredDatasources(searchKeyword);
|
||||||
|
|
||||||
|
const plugins = useSelector(getPlugins);
|
||||||
|
|
||||||
|
const widgets = useWidgets(searchKeyword);
|
||||||
|
const actions = useActions(searchKeyword);
|
||||||
|
|
||||||
|
let noResults = false;
|
||||||
|
if (searchKeyword) {
|
||||||
|
const noWidgets = Object.values(widgets).filter(Boolean).length === 0;
|
||||||
|
const noActions =
|
||||||
|
Object.values(actions).filter(actions => actions && actions.length > 0)
|
||||||
|
.length === 0;
|
||||||
|
const noDatasource =
|
||||||
|
Object.values(datasources).filter(
|
||||||
|
datasources => datasources && datasources.length > 0,
|
||||||
|
).length === 0;
|
||||||
|
noResults = noWidgets && noActions && noDatasource;
|
||||||
|
}
|
||||||
|
const { openPanel } = props;
|
||||||
|
const showWidgetsSidebar = useCallback(
|
||||||
|
(pageId: string) => {
|
||||||
|
history.push(BUILDER_PAGE_URL(applicationId, pageId));
|
||||||
|
openPanel({ component: WidgetSidebar });
|
||||||
|
},
|
||||||
|
[openPanel, applicationId],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Wrapper ref={explorerRef}>
|
||||||
|
<Search ref={searchInputRef} clear={clearSearch} />
|
||||||
|
<ExplorerPageGroup
|
||||||
|
searchKeyword={searchKeyword}
|
||||||
|
step={0}
|
||||||
|
widgets={widgets}
|
||||||
|
actions={actions}
|
||||||
|
datasources={datasources}
|
||||||
|
plugins={plugins}
|
||||||
|
showWidgetsSidebar={showWidgetsSidebar}
|
||||||
|
/>
|
||||||
|
{noResults && (
|
||||||
|
<NoResult
|
||||||
|
className={Classes.DARK}
|
||||||
|
description="Try modifying the search keyword."
|
||||||
|
title="No entities found"
|
||||||
|
icon="search"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<StyledDivider />
|
||||||
|
<JSDependencies />
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ExplorerContent;
|
EntityExplorer.displayName = "EntityExplorer";
|
||||||
|
|
||||||
|
EntityExplorer.whyDidYouRender = {
|
||||||
|
logOnDifferentValues: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EntityExplorer;
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@ import {
|
||||||
isPathADynamicProperty,
|
isPathADynamicProperty,
|
||||||
isPathADynamicTrigger,
|
isPathADynamicTrigger,
|
||||||
} from "../../../utils/DynamicBindingUtils";
|
} from "../../../utils/DynamicBindingUtils";
|
||||||
import OnboardingToolTip from "components/editorComponents/Onboarding/Tooltip";
|
|
||||||
import { Position } from "@blueprintjs/core";
|
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
widgetProperties: WidgetProps;
|
widgetProperties: WidgetProps;
|
||||||
|
|
@ -116,22 +113,13 @@ const PropertyControl = (props: Props) => {
|
||||||
</JSToggleButton>
|
</JSToggleButton>
|
||||||
)}
|
)}
|
||||||
</ControlPropertyLabelContainer>
|
</ControlPropertyLabelContainer>
|
||||||
<OnboardingToolTip
|
{PropertyControlFactory.createControl(
|
||||||
step={[
|
config,
|
||||||
OnboardingStep.ADD_WIDGET,
|
{
|
||||||
OnboardingStep.SUCCESSFUL_BINDING,
|
onPropertyChange: onPropertyChange,
|
||||||
]}
|
},
|
||||||
show={propertyName === "tableData"}
|
isDynamic,
|
||||||
position={Position.LEFT_TOP}
|
)}
|
||||||
>
|
|
||||||
{PropertyControlFactory.createControl(
|
|
||||||
config,
|
|
||||||
{
|
|
||||||
onPropertyChange: onPropertyChange,
|
|
||||||
},
|
|
||||||
isDynamic,
|
|
||||||
)}
|
|
||||||
</OnboardingToolTip>
|
|
||||||
</ControlWrapper>
|
</ControlWrapper>
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ class TemplateMenu extends React.Component<Props> {
|
||||||
onClick={() => createTemplate("")}
|
onClick={() => createTemplate("")}
|
||||||
>
|
>
|
||||||
<div style={{ fontSize: 14 }}>
|
<div style={{ fontSize: 14 }}>
|
||||||
Click here to start with a blank state or select a template.
|
Press enter to start with a blank state or select a template.
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: "6px" }}>
|
<div style={{ marginTop: "6px" }}>
|
||||||
{Object.entries(pluginTemplates).map(template => {
|
{Object.entries(pluginTemplates).map(template => {
|
||||||
|
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import Spinner from "components/ads/Spinner";
|
|
||||||
import { Classes } from "components/ads/common";
|
|
||||||
import { AppState } from "reducers";
|
|
||||||
import { endOnboarding, setCurrentStep } from "actions/onboardingActions";
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
height: 100%;
|
|
||||||
padding: 85px 55px;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
align-self: stretch;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
background-color: white;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 80px 0px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const WelcomeText = styled.div`
|
|
||||||
font-size: 36px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #090707;
|
|
||||||
text-align: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Description = styled.div`
|
|
||||||
font-size: 17px;
|
|
||||||
color: #716e6e;
|
|
||||||
margin-top: 16px;
|
|
||||||
text-align: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const NotNewUserText = styled.span`
|
|
||||||
color: #716e6e;
|
|
||||||
margin-top: 24px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: #457ae6;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledButton = styled.button`
|
|
||||||
color: white;
|
|
||||||
background-color: #f3672a;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 17px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LoadingContainer = styled(Container)`
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0px;
|
|
||||||
|
|
||||||
.${Classes.SPINNER} {
|
|
||||||
width: 43px;
|
|
||||||
height: 43px;
|
|
||||||
|
|
||||||
circle {
|
|
||||||
stroke: #f3672a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 17px;
|
|
||||||
margin-top: 23px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Welcome = () => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const creatingDatabase = useSelector(
|
|
||||||
(state: AppState) => state.ui.onBoarding.creatingDatabase,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (creatingDatabase) {
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<LoadingContainer>
|
|
||||||
<Spinner />
|
|
||||||
<span>Creating Example Database</span>
|
|
||||||
</LoadingContainer>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<Container>
|
|
||||||
<div>
|
|
||||||
<WelcomeText>
|
|
||||||
<span role="img" aria-label="hello">
|
|
||||||
👋
|
|
||||||
</span>{" "}
|
|
||||||
Welcome
|
|
||||||
</WelcomeText>
|
|
||||||
<Description>
|
|
||||||
Appsmith helps you build quality internal tools, fast!
|
|
||||||
</Description>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledButton
|
|
||||||
className="t--create-database"
|
|
||||||
onClick={() => {
|
|
||||||
dispatch(setCurrentStep(1));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Explore Appsmith
|
|
||||||
</StyledButton>
|
|
||||||
<NotNewUserText>
|
|
||||||
Not your first time with Appsmith?{" "}
|
|
||||||
<span onClick={() => dispatch(endOnboarding())}>
|
|
||||||
Skip this tutorial
|
|
||||||
</span>
|
|
||||||
</NotNewUserText>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Welcome;
|
|
||||||
|
|
@ -11,8 +11,6 @@ import ExplorerSearch from "./Explorer/ExplorerSearch";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import produce from "immer";
|
import produce from "immer";
|
||||||
import { WIDGET_SIDEBAR_CAPTION } from "constants/messages";
|
import { WIDGET_SIDEBAR_CAPTION } from "constants/messages";
|
||||||
import Boxed from "components/editorComponents/Onboarding/Boxed";
|
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
|
||||||
|
|
||||||
const MainWrapper = styled.div`
|
const MainWrapper = styled.div`
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
|
@ -145,18 +143,10 @@ const WidgetSidebar = (props: IPanelProps) => {
|
||||||
</Header>
|
</Header>
|
||||||
{groups.map((group: string) => (
|
{groups.map((group: string) => (
|
||||||
<React.Fragment key={group}>
|
<React.Fragment key={group}>
|
||||||
<Boxed step={OnboardingStep.ADD_WIDGET}>
|
<h5>{group}</h5>
|
||||||
<h5>{group}</h5>
|
|
||||||
</Boxed>
|
|
||||||
<CardsWrapper>
|
<CardsWrapper>
|
||||||
{filteredCards[group].map((card: WidgetCardProps) => (
|
{filteredCards[group].map((card: WidgetCardProps) => (
|
||||||
<Boxed
|
<WidgetCard details={card} key={card.key} />
|
||||||
step={OnboardingStep.ADD_WIDGET}
|
|
||||||
show={card.type === "TABLE_WIDGET"}
|
|
||||||
key={card.key}
|
|
||||||
>
|
|
||||||
<WidgetCard details={card} />
|
|
||||||
</Boxed>
|
|
||||||
))}
|
))}
|
||||||
</CardsWrapper>
|
</CardsWrapper>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import React, { useEffect, ReactNode, useCallback } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import Canvas from "./Canvas";
|
import Canvas from "./Canvas";
|
||||||
import Welcome from "./Welcome";
|
|
||||||
import {
|
import {
|
||||||
getIsFetchingPage,
|
getIsFetchingPage,
|
||||||
getCurrentPageId,
|
getCurrentPageId,
|
||||||
|
|
@ -22,7 +21,6 @@ import { fetchPage } from "actions/pageActions";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
import { AppState } from "reducers";
|
|
||||||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
import { getCurrentApplication } from "selectors/applicationSelectors";
|
||||||
|
|
||||||
const EditorWrapper = styled.div`
|
const EditorWrapper = styled.div`
|
||||||
|
|
@ -62,9 +60,6 @@ const WidgetsEditor = () => {
|
||||||
const currentPageId = useSelector(getCurrentPageId);
|
const currentPageId = useSelector(getCurrentPageId);
|
||||||
const currentPageName = useSelector(getCurrentPageName);
|
const currentPageName = useSelector(getCurrentPageName);
|
||||||
const currentApp = useSelector(getCurrentApplication);
|
const currentApp = useSelector(getCurrentApplication);
|
||||||
const showWelcomeScreen = useSelector(
|
|
||||||
(state: AppState) => state.ui.onBoarding.showWelcomeScreen,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
PerformanceTracker.stopTracking(PerformanceTransactionName.EDITOR_MOUNT);
|
PerformanceTracker.stopTracking(PerformanceTransactionName.EDITOR_MOUNT);
|
||||||
|
|
@ -117,11 +112,6 @@ const WidgetsEditor = () => {
|
||||||
if (!isFetchingPage && widgets) {
|
if (!isFetchingPage && widgets) {
|
||||||
node = <Canvas dsl={widgets} />;
|
node = <Canvas dsl={widgets} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showWelcomeScreen) {
|
|
||||||
return <Welcome />;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Canvas rendered");
|
log.debug("Canvas rendered");
|
||||||
PerformanceTracker.stopTracking();
|
PerformanceTracker.stopTracking();
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ import React, { Component } from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { RouteComponentProps, withRouter } from "react-router-dom";
|
import { RouteComponentProps, withRouter } from "react-router-dom";
|
||||||
import { BuilderRouteParams } from "constants/routes";
|
import {
|
||||||
|
BuilderRouteParams,
|
||||||
|
getApplicationViewerPageURL,
|
||||||
|
} from "constants/routes";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import MainContainer from "./MainContainer";
|
import MainContainer from "./MainContainer";
|
||||||
import { DndProvider } from "react-dnd";
|
import { DndProvider } from "react-dnd";
|
||||||
|
|
@ -15,7 +18,14 @@ import {
|
||||||
getIsPublishingApplication,
|
getIsPublishingApplication,
|
||||||
getPublishingError,
|
getPublishingError,
|
||||||
} from "selectors/editorSelectors";
|
} from "selectors/editorSelectors";
|
||||||
import { Hotkey, Hotkeys, Spinner } from "@blueprintjs/core";
|
import {
|
||||||
|
AnchorButton,
|
||||||
|
Classes,
|
||||||
|
Dialog,
|
||||||
|
Hotkey,
|
||||||
|
Hotkeys,
|
||||||
|
Spinner,
|
||||||
|
} from "@blueprintjs/core";
|
||||||
import { HotkeysTarget } from "@blueprintjs/core/lib/esnext/components/hotkeys/hotkeysTarget.js";
|
import { HotkeysTarget } from "@blueprintjs/core/lib/esnext/components/hotkeys/hotkeysTarget.js";
|
||||||
import { initEditor } from "actions/initActions";
|
import { initEditor } from "actions/initActions";
|
||||||
import { editorInitializer } from "utils/EditorUtils";
|
import { editorInitializer } from "utils/EditorUtils";
|
||||||
|
|
@ -80,7 +90,7 @@ class Editor extends Component<Props> {
|
||||||
combo="mod + c"
|
combo="mod + c"
|
||||||
label="Copy Widget"
|
label="Copy Widget"
|
||||||
group="Canvas"
|
group="Canvas"
|
||||||
onKeyDown={() => {
|
onKeyDown={(e: any) => {
|
||||||
this.props.copySelectedWidget();
|
this.props.copySelectedWidget();
|
||||||
}}
|
}}
|
||||||
preventDefault
|
preventDefault
|
||||||
|
|
@ -91,7 +101,7 @@ class Editor extends Component<Props> {
|
||||||
combo="mod + v"
|
combo="mod + v"
|
||||||
label="Paste Widget"
|
label="Paste Widget"
|
||||||
group="Canvas"
|
group="Canvas"
|
||||||
onKeyDown={() => {
|
onKeyDown={(e: any) => {
|
||||||
this.props.pasteCopiedWidget();
|
this.props.pasteCopiedWidget();
|
||||||
}}
|
}}
|
||||||
preventDefault
|
preventDefault
|
||||||
|
|
@ -102,7 +112,7 @@ class Editor extends Component<Props> {
|
||||||
combo="del"
|
combo="del"
|
||||||
label="Delete Widget"
|
label="Delete Widget"
|
||||||
group="Canvas"
|
group="Canvas"
|
||||||
onKeyDown={() => {
|
onKeyDown={(e: any) => {
|
||||||
if (!isMac()) this.props.deleteSelectedWidget();
|
if (!isMac()) this.props.deleteSelectedWidget();
|
||||||
}}
|
}}
|
||||||
preventDefault
|
preventDefault
|
||||||
|
|
@ -113,7 +123,7 @@ class Editor extends Component<Props> {
|
||||||
combo="backspace"
|
combo="backspace"
|
||||||
label="Delete Widget"
|
label="Delete Widget"
|
||||||
group="Canvas"
|
group="Canvas"
|
||||||
onKeyDown={() => {
|
onKeyDown={(e: any) => {
|
||||||
if (isMac()) this.props.deleteSelectedWidget();
|
if (isMac()) this.props.deleteSelectedWidget();
|
||||||
}}
|
}}
|
||||||
preventDefault
|
preventDefault
|
||||||
|
|
@ -124,7 +134,7 @@ class Editor extends Component<Props> {
|
||||||
combo="del"
|
combo="del"
|
||||||
label="Delete Widget"
|
label="Delete Widget"
|
||||||
group="Canvas"
|
group="Canvas"
|
||||||
onKeyDown={() => {
|
onKeyDown={(e: any) => {
|
||||||
this.props.deleteSelectedWidget();
|
this.props.deleteSelectedWidget();
|
||||||
}}
|
}}
|
||||||
preventDefault
|
preventDefault
|
||||||
|
|
@ -135,7 +145,7 @@ class Editor extends Component<Props> {
|
||||||
combo="mod + x"
|
combo="mod + x"
|
||||||
label="Cut Widget"
|
label="Cut Widget"
|
||||||
group="Canvas"
|
group="Canvas"
|
||||||
onKeyDown={() => {
|
onKeyDown={(e: any) => {
|
||||||
this.props.cutSelectedWidget();
|
this.props.cutSelectedWidget();
|
||||||
}}
|
}}
|
||||||
preventDefault
|
preventDefault
|
||||||
|
|
@ -145,6 +155,7 @@ class Editor extends Component<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public state = {
|
public state = {
|
||||||
|
isDialogOpen: false,
|
||||||
registered: false,
|
registered: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -157,8 +168,21 @@ class Editor extends Component<Props> {
|
||||||
this.props.initEditor(applicationId, pageId);
|
this.props.initEditor(applicationId, pageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
componentDidUpdate(previously: Props) {
|
||||||
|
if (
|
||||||
|
previously.isPublishing &&
|
||||||
|
!(this.props.isPublishing || this.props.errorPublishing)
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
isDialogOpen: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Props, nextState: { registered: boolean }) {
|
shouldComponentUpdate(
|
||||||
|
nextProps: Props,
|
||||||
|
nextState: { isDialogOpen: boolean; registered: boolean },
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
nextProps.currentPageId !== this.props.currentPageId ||
|
nextProps.currentPageId !== this.props.currentPageId ||
|
||||||
nextProps.currentApplicationId !== this.props.currentApplicationId ||
|
nextProps.currentApplicationId !== this.props.currentApplicationId ||
|
||||||
|
|
@ -168,10 +192,16 @@ class Editor extends Component<Props> {
|
||||||
nextProps.errorPublishing !== this.props.errorPublishing ||
|
nextProps.errorPublishing !== this.props.errorPublishing ||
|
||||||
nextProps.isEditorInitializeError !==
|
nextProps.isEditorInitializeError !==
|
||||||
this.props.isEditorInitializeError ||
|
this.props.isEditorInitializeError ||
|
||||||
|
nextState.isDialogOpen !== this.state.isDialogOpen ||
|
||||||
nextState.registered !== this.state.registered
|
nextState.registered !== this.state.registered
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDialogClose = () => {
|
||||||
|
this.setState({
|
||||||
|
isDialogOpen: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
public render() {
|
public render() {
|
||||||
if (!this.props.isEditorInitialized || !this.state.registered) {
|
if (!this.props.isEditorInitialized || !this.state.registered) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -193,6 +223,32 @@ class Editor extends Component<Props> {
|
||||||
<title>Editor | Appsmith</title>
|
<title>Editor | Appsmith</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<MainContainer />
|
<MainContainer />
|
||||||
|
<Dialog
|
||||||
|
isOpen={this.state.isDialogOpen}
|
||||||
|
canOutsideClickClose={true}
|
||||||
|
canEscapeKeyClose={true}
|
||||||
|
title="Application Published"
|
||||||
|
onClose={this.handleDialogClose}
|
||||||
|
icon="tick-circle"
|
||||||
|
>
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<p>
|
||||||
|
{"Your application is now published with the current changes!"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<AnchorButton
|
||||||
|
target={this.props.currentApplicationId}
|
||||||
|
href={getApplicationViewerPageURL(
|
||||||
|
this.props.currentApplicationId,
|
||||||
|
this.props.currentPageId,
|
||||||
|
)}
|
||||||
|
text="View Application"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmRunModal />
|
<ConfirmRunModal />
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ import { AppState } from "reducers";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
import { setOnboardingState } from "utils/storage";
|
|
||||||
const {
|
const {
|
||||||
enableGithubOAuth,
|
enableGithubOAuth,
|
||||||
enableGoogleOAuth,
|
enableGoogleOAuth,
|
||||||
|
|
@ -161,7 +160,6 @@ export const SignUp = (props: SignUpFormProps) => {
|
||||||
PerformanceTracker.startTracking(
|
PerformanceTracker.startTracking(
|
||||||
PerformanceTransactionName.SIGN_UP,
|
PerformanceTransactionName.SIGN_UP,
|
||||||
);
|
);
|
||||||
setOnboardingState(true);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormActions>
|
</FormActions>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import { useLocation } from "react-router-dom";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
import { setOnboardingState } from "utils/storage";
|
|
||||||
|
|
||||||
const ThirdPartyAuthWrapper = styled.div`
|
const ThirdPartyAuthWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -87,9 +86,6 @@ const SocialLoginButton = (props: {
|
||||||
let eventName: EventName = "LOGIN_CLICK";
|
let eventName: EventName = "LOGIN_CLICK";
|
||||||
if (props.type === "SIGNUP") {
|
if (props.type === "SIGNUP") {
|
||||||
eventName = "SIGNUP_CLICK";
|
eventName = "SIGNUP_CLICK";
|
||||||
|
|
||||||
// Set onboarding flag on signup
|
|
||||||
setOnboardingState(true);
|
|
||||||
}
|
}
|
||||||
PerformanceTracker.startTracking(
|
PerformanceTracker.startTracking(
|
||||||
eventName === "SIGNUP_CLICK"
|
eventName === "SIGNUP_CLICK"
|
||||||
|
|
|
||||||
|
|
@ -397,38 +397,6 @@ const actionsReducer = createReducer(initialState, {
|
||||||
return action;
|
return action;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[ReduxActionTypes.CREATE_ONBOARDING_ACTION_SUCCESS]: (
|
|
||||||
state: ActionDataState,
|
|
||||||
action: ReduxAction<RestAction>,
|
|
||||||
): ActionDataState =>
|
|
||||||
state.map(a => {
|
|
||||||
if (
|
|
||||||
a.config.pageId === action.payload.pageId &&
|
|
||||||
a.config.id === action.payload.name
|
|
||||||
) {
|
|
||||||
return { ...a, config: action.payload };
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}),
|
|
||||||
[ReduxActionTypes.CREATE_ONBOARDING_ACTION_INIT]: (
|
|
||||||
state: ActionDataState,
|
|
||||||
action: ReduxAction<RestAction>,
|
|
||||||
): ActionDataState =>
|
|
||||||
state.concat([
|
|
||||||
{
|
|
||||||
config: { ...action.payload, id: action.payload.name },
|
|
||||||
isLoading: false,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
[ReduxActionTypes.CREATE_ONBOARDING_ACTION_ERROR]: (
|
|
||||||
state: ActionDataState,
|
|
||||||
action: ReduxAction<RestAction>,
|
|
||||||
): ActionDataState =>
|
|
||||||
state.filter(
|
|
||||||
a =>
|
|
||||||
a.config.name !== action.payload.name &&
|
|
||||||
a.config.id !== action.payload.name,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default actionsReducer;
|
export default actionsReducer;
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ import { DatasourceNameReduxState } from "./uiReducers/datasourceNameReducer";
|
||||||
import { EvaluatedTreeState } from "./evaluationReducers/treeReducer";
|
import { EvaluatedTreeState } from "./evaluationReducers/treeReducer";
|
||||||
import { EvaluationDependencyState } from "./evaluationReducers/dependencyReducer";
|
import { EvaluationDependencyState } from "./evaluationReducers/dependencyReducer";
|
||||||
import { PageWidgetsReduxState } from "./uiReducers/pageWidgetsReducer";
|
import { PageWidgetsReduxState } from "./uiReducers/pageWidgetsReducer";
|
||||||
import { OnboardingState } from "./uiReducers/onBoardingReducer";
|
|
||||||
|
|
||||||
const appReducer = combineReducers({
|
const appReducer = combineReducers({
|
||||||
entities: entityReducer,
|
entities: entityReducer,
|
||||||
|
|
@ -75,7 +74,6 @@ export interface AppState {
|
||||||
confirmRunAction: ConfirmRunActionReduxState;
|
confirmRunAction: ConfirmRunActionReduxState;
|
||||||
datasourceName: DatasourceNameReduxState;
|
datasourceName: DatasourceNameReduxState;
|
||||||
theme: ThemeState;
|
theme: ThemeState;
|
||||||
onBoarding: OnboardingState;
|
|
||||||
};
|
};
|
||||||
entities: {
|
entities: {
|
||||||
canvasWidgets: CanvasWidgetsReduxState;
|
canvasWidgets: CanvasWidgetsReduxState;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import themeReducer from "./themeReducer";
|
||||||
import datasourceNameReducer from "./datasourceNameReducer";
|
import datasourceNameReducer from "./datasourceNameReducer";
|
||||||
import pageCanvasStructureReducer from "./pageCanvasStructure";
|
import pageCanvasStructureReducer from "./pageCanvasStructure";
|
||||||
import pageWidgetsReducer from "./pageWidgetsReducer";
|
import pageWidgetsReducer from "./pageWidgetsReducer";
|
||||||
import onBoardingReducer from "./onBoardingReducer";
|
|
||||||
|
|
||||||
const uiReducer = combineReducers({
|
const uiReducer = combineReducers({
|
||||||
widgetSidebar: widgetSidebarReducer,
|
widgetSidebar: widgetSidebarReducer,
|
||||||
|
|
@ -50,6 +49,5 @@ const uiReducer = combineReducers({
|
||||||
pageWidgets: pageWidgetsReducer,
|
pageWidgets: pageWidgetsReducer,
|
||||||
theme: themeReducer,
|
theme: themeReducer,
|
||||||
confirmRunAction: confirmRunActionReducer,
|
confirmRunAction: confirmRunActionReducer,
|
||||||
onBoarding: onBoardingReducer,
|
|
||||||
});
|
});
|
||||||
export default uiReducer;
|
export default uiReducer;
|
||||||
|
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
|
||||||
import {
|
|
||||||
ReduxAction,
|
|
||||||
ReduxActionTypes,
|
|
||||||
ReduxActionErrorTypes,
|
|
||||||
} from "constants/ReduxActionConstants";
|
|
||||||
import { createReducer } from "utils/AppsmithUtils";
|
|
||||||
|
|
||||||
const initialState: OnboardingState = {
|
|
||||||
currentStep: OnboardingStep.NONE,
|
|
||||||
showWelcomeScreen: false,
|
|
||||||
creatingDatabase: false,
|
|
||||||
showCompletionDialog: false,
|
|
||||||
inOnboarding: false,
|
|
||||||
createdDBQuery: false,
|
|
||||||
addedWidget: false,
|
|
||||||
showingTooltip: OnboardingStep.NONE,
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface OnboardingState {
|
|
||||||
currentStep: OnboardingStep;
|
|
||||||
showWelcomeScreen: boolean;
|
|
||||||
creatingDatabase: boolean;
|
|
||||||
showCompletionDialog: boolean;
|
|
||||||
inOnboarding: boolean;
|
|
||||||
createdDBQuery: boolean;
|
|
||||||
addedWidget: boolean;
|
|
||||||
// Tooltip is shown when the step matches this value
|
|
||||||
showingTooltip: OnboardingStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onboardingReducer = createReducer(initialState, {
|
|
||||||
[ReduxActionTypes.SHOW_WELCOME]: (state: OnboardingState) => {
|
|
||||||
return { ...state, showWelcomeScreen: true };
|
|
||||||
},
|
|
||||||
[ReduxActionTypes.CREATE_ONBOARDING_DBQUERY_INIT]: (
|
|
||||||
state: OnboardingState,
|
|
||||||
) => {
|
|
||||||
return { ...state, creatingDatabase: true };
|
|
||||||
},
|
|
||||||
[ReduxActionTypes.CREATE_ONBOARDING_DBQUERY_SUCCESS]: (
|
|
||||||
state: OnboardingState,
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
creatingDatabase: false,
|
|
||||||
showWelcomeScreen: false,
|
|
||||||
createdDBQuery: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ReduxActionErrorTypes.CREATE_ONBOARDING_DBQUERY_ERROR]: (
|
|
||||||
state: OnboardingState,
|
|
||||||
) => {
|
|
||||||
return { ...state, creatingDatabase: false };
|
|
||||||
},
|
|
||||||
[ReduxActionTypes.INCREMENT_STEP]: (state: OnboardingState) => {
|
|
||||||
return { ...state, currentStep: state.currentStep + 1 };
|
|
||||||
},
|
|
||||||
[ReduxActionTypes.SET_CURRENT_STEP]: (
|
|
||||||
state: OnboardingState,
|
|
||||||
action: ReduxAction<number>,
|
|
||||||
) => {
|
|
||||||
return { ...state, currentStep: action.payload };
|
|
||||||
},
|
|
||||||
[ReduxActionTypes.SET_ONBOARDING_STATE]: (
|
|
||||||
state: OnboardingState,
|
|
||||||
action: ReduxAction<boolean>,
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
...initialState,
|
|
||||||
inOnboarding: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ReduxActionTypes.ADD_WIDGET_COMPLETE]: (state: OnboardingState) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
addedWidget: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ReduxActionTypes.SHOW_ONBOARDING_TOOLTIP]: (
|
|
||||||
state: OnboardingState,
|
|
||||||
action: ReduxAction<number>,
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
showingTooltip: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ReduxActionTypes.SHOW_ONBOARDING_COMPLETION_DIALOG]: (
|
|
||||||
state: OnboardingState,
|
|
||||||
action: ReduxAction<boolean>,
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
showCompletionDialog: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default onboardingReducer;
|
|
||||||
|
|
@ -67,9 +67,7 @@ import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
|
|
||||||
export function* createActionSaga(
|
export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
|
||||||
actionPayload: ReduxAction<Partial<Action> & { eventData: any }>,
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const response: ActionCreateUpdateResponse = yield ActionAPI.createAPI(
|
const response: ActionCreateUpdateResponse = yield ActionAPI.createAPI(
|
||||||
actionPayload.payload,
|
actionPayload.payload,
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,7 @@ import { validateResponse } from "./ErrorSagas";
|
||||||
import { getUserApplicationsOrgsList } from "selectors/applicationSelectors";
|
import { getUserApplicationsOrgsList } from "selectors/applicationSelectors";
|
||||||
import { ApiResponse } from "api/ApiResponses";
|
import { ApiResponse } from "api/ApiResponses";
|
||||||
import history from "utils/history";
|
import history from "utils/history";
|
||||||
import {
|
import { BUILDER_PAGE_URL } from "constants/routes";
|
||||||
BUILDER_PAGE_URL,
|
|
||||||
getApplicationViewerPageURL,
|
|
||||||
} from "constants/routes";
|
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import {
|
import {
|
||||||
FetchApplicationPayload,
|
FetchApplicationPayload,
|
||||||
|
|
@ -46,11 +43,6 @@ import { Organization } from "constants/orgConstants";
|
||||||
import { Variant } from "components/ads/common";
|
import { Variant } from "components/ads/common";
|
||||||
import { AppIconName } from "components/ads/AppIcon";
|
import { AppIconName } from "components/ads/AppIcon";
|
||||||
import { AppColorCode } from "constants/DefaultTheme";
|
import { AppColorCode } from "constants/DefaultTheme";
|
||||||
import {
|
|
||||||
getCurrentApplicationId,
|
|
||||||
getCurrentPageId,
|
|
||||||
} from "selectors/editorSelectors";
|
|
||||||
import { showCompletionDialog } from "./OnboardingSagas";
|
|
||||||
|
|
||||||
const getDefaultPageId = (
|
const getDefaultPageId = (
|
||||||
pages?: ApplicationPagePayload[],
|
pages?: ApplicationPagePayload[],
|
||||||
|
|
@ -79,20 +71,6 @@ export function* publishApplicationSaga(
|
||||||
yield put({
|
yield put({
|
||||||
type: ReduxActionTypes.PUBLISH_APPLICATION_SUCCESS,
|
type: ReduxActionTypes.PUBLISH_APPLICATION_SUCCESS,
|
||||||
});
|
});
|
||||||
|
|
||||||
const applicationId = yield select(getCurrentApplicationId);
|
|
||||||
const currentPageId = yield select(getCurrentPageId);
|
|
||||||
let appicationViewPageUrl = getApplicationViewerPageURL(
|
|
||||||
applicationId,
|
|
||||||
currentPageId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const showOnboardingCompletionDialog = yield select(showCompletionDialog);
|
|
||||||
if (showOnboardingCompletionDialog) {
|
|
||||||
appicationViewPageUrl += "?onboardingComplete=true";
|
|
||||||
}
|
|
||||||
|
|
||||||
window.open(appicationViewPageUrl, "_blank");
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put({
|
yield put({
|
||||||
|
|
|
||||||
|
|
@ -1,343 +0,0 @@
|
||||||
import { GenericApiResponse } from "api/ApiResponses";
|
|
||||||
import DatasourcesApi, { Datasource } from "api/DatasourcesApi";
|
|
||||||
import { Plugin } from "api/PluginApi";
|
|
||||||
import {
|
|
||||||
ReduxActionErrorTypes,
|
|
||||||
ReduxActionTypes,
|
|
||||||
} from "constants/ReduxActionConstants";
|
|
||||||
import { AppState } from "reducers";
|
|
||||||
import { all, delay, put, select, take, takeEvery } from "redux-saga/effects";
|
|
||||||
import { getCurrentPageId } from "selectors/editorSelectors";
|
|
||||||
import { getDatasources, getPlugins } from "selectors/entitiesSelector";
|
|
||||||
import { getDataTree } from "selectors/dataTreeSelectors";
|
|
||||||
import { getCurrentOrgId } from "selectors/organizationSelectors";
|
|
||||||
import { getOnboardingState, setOnboardingState } from "utils/storage";
|
|
||||||
import { validateResponse } from "./ErrorSagas";
|
|
||||||
import { getSelectedWidget } from "./selectors";
|
|
||||||
import ActionAPI, {
|
|
||||||
ActionApiResponse,
|
|
||||||
ActionCreateUpdateResponse,
|
|
||||||
} from "api/ActionAPI";
|
|
||||||
import {
|
|
||||||
createOnboardingActionInit,
|
|
||||||
createOnboardingActionSuccess,
|
|
||||||
setCurrentStep,
|
|
||||||
setOnboardingState as setOnboardingReduxState,
|
|
||||||
showTooltip,
|
|
||||||
} from "actions/onboardingActions";
|
|
||||||
import {
|
|
||||||
changeDatasource,
|
|
||||||
expandDatasourceEntity,
|
|
||||||
} from "actions/datasourceActions";
|
|
||||||
import { playOnboardingAnimation } from "utils/helpers";
|
|
||||||
import { QueryAction } from "entities/Action";
|
|
||||||
import { getActionTimeout } from "./ActionExecutionSagas";
|
|
||||||
import {
|
|
||||||
OnboardingConfig,
|
|
||||||
OnboardingStep,
|
|
||||||
} from "constants/OnboardingConstants";
|
|
||||||
import AnalyticsUtil from "../utils/AnalyticsUtil";
|
|
||||||
|
|
||||||
export const getCurrentStep = (state: AppState) =>
|
|
||||||
state.ui.onBoarding.currentStep;
|
|
||||||
export const inOnboarding = (state: AppState) =>
|
|
||||||
state.ui.onBoarding.inOnboarding;
|
|
||||||
export const isAddWidgetComplete = (state: AppState) =>
|
|
||||||
state.ui.onBoarding.addedWidget;
|
|
||||||
export const getTooltipConfig = (state: AppState) => {
|
|
||||||
const currentStep = getCurrentStep(state);
|
|
||||||
if (currentStep >= 0) {
|
|
||||||
return OnboardingConfig[currentStep].tooltip;
|
|
||||||
}
|
|
||||||
|
|
||||||
return OnboardingConfig[OnboardingStep.NONE].tooltip;
|
|
||||||
};
|
|
||||||
export const showCompletionDialog = (state: AppState) => {
|
|
||||||
const isInOnboarding = inOnboarding(state);
|
|
||||||
const currentStep = getCurrentStep(state);
|
|
||||||
|
|
||||||
return isInOnboarding && currentStep === OnboardingStep.DEPLOY;
|
|
||||||
};
|
|
||||||
|
|
||||||
function* listenForWidgetAdditions() {
|
|
||||||
while (true) {
|
|
||||||
yield take();
|
|
||||||
const { payload } = yield take("WIDGET_ADD_CHILD");
|
|
||||||
|
|
||||||
if (payload.type === "TABLE_WIDGET") {
|
|
||||||
yield put(setCurrentStep(OnboardingStep.ADD_WIDGET));
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.ADD_WIDGET_COMPLETE,
|
|
||||||
});
|
|
||||||
yield put(showTooltip(OnboardingStep.ADD_WIDGET));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* listenForSuccessfullBinding() {
|
|
||||||
while (true) {
|
|
||||||
yield take();
|
|
||||||
|
|
||||||
let bindSuccessfull = true;
|
|
||||||
const selectedWidget = yield select(getSelectedWidget);
|
|
||||||
if (selectedWidget && selectedWidget.type === "TABLE_WIDGET") {
|
|
||||||
const dataTree = yield select(getDataTree);
|
|
||||||
|
|
||||||
if (dataTree[selectedWidget.widgetName]) {
|
|
||||||
const widgetProperties = dataTree[selectedWidget.widgetName];
|
|
||||||
const dynamicBindingPathList =
|
|
||||||
dataTree[selectedWidget.widgetName].dynamicBindingPathList;
|
|
||||||
const hasBinding = !!dynamicBindingPathList.length;
|
|
||||||
|
|
||||||
if (hasBinding) {
|
|
||||||
yield put(showTooltip(OnboardingStep.NONE));
|
|
||||||
}
|
|
||||||
|
|
||||||
bindSuccessfull = bindSuccessfull && hasBinding;
|
|
||||||
|
|
||||||
if (widgetProperties.invalidProps) {
|
|
||||||
bindSuccessfull =
|
|
||||||
bindSuccessfull && !("tableData" in widgetProperties.invalidProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bindSuccessfull) {
|
|
||||||
yield put(setCurrentStep(OnboardingStep.SUCCESSFUL_BINDING));
|
|
||||||
|
|
||||||
// Show tooltip now
|
|
||||||
yield put(showTooltip(OnboardingStep.SUCCESSFUL_BINDING));
|
|
||||||
yield delay(1000);
|
|
||||||
playOnboardingAnimation();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* hideDatabaseTooltip() {
|
|
||||||
yield take([ReduxActionTypes.QUERY_PANE_CHANGE]);
|
|
||||||
|
|
||||||
yield put(showTooltip(OnboardingStep.NONE));
|
|
||||||
}
|
|
||||||
|
|
||||||
function* createOnboardingDatasource() {
|
|
||||||
try {
|
|
||||||
const organizationId = yield select(getCurrentOrgId);
|
|
||||||
const plugins = yield select(getPlugins);
|
|
||||||
const postgresPlugin = plugins.find(
|
|
||||||
(plugin: Plugin) => plugin.name === "PostgreSQL",
|
|
||||||
);
|
|
||||||
const datasources: Datasource[] = yield select(getDatasources);
|
|
||||||
let onboardingDatasource = datasources.find(
|
|
||||||
datasource => datasource.name === "ExampleDatabase",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!onboardingDatasource) {
|
|
||||||
const datasourceConfig: any = {
|
|
||||||
pluginId: postgresPlugin.id,
|
|
||||||
name: "ExampleDatabase",
|
|
||||||
organizationId,
|
|
||||||
datasourceConfiguration: {
|
|
||||||
endpoints: [
|
|
||||||
{
|
|
||||||
host: "fake-api.cvuydmurdlas.us-east-1.rds.amazonaws.com",
|
|
||||||
port: 5432,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
authentication: {
|
|
||||||
databaseName: "fakeapi",
|
|
||||||
username: "fakeapi",
|
|
||||||
password: "LimitedAccess123#",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const datasourceResponse: GenericApiResponse<Datasource> = yield DatasourcesApi.createDatasource(
|
|
||||||
datasourceConfig,
|
|
||||||
);
|
|
||||||
yield validateResponse(datasourceResponse);
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.CREATE_DATASOURCE_SUCCESS,
|
|
||||||
payload: datasourceResponse.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
onboardingDatasource = datasourceResponse.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentPageId = yield select(getCurrentPageId);
|
|
||||||
const queryactionConfiguration: Partial<QueryAction> = {
|
|
||||||
actionConfiguration: { body: "select * from public.users limit 10" },
|
|
||||||
};
|
|
||||||
const actionPayload = {
|
|
||||||
name: "ExampleQuery",
|
|
||||||
pageId: currentPageId,
|
|
||||||
datasource: {
|
|
||||||
id: onboardingDatasource.id,
|
|
||||||
},
|
|
||||||
...queryactionConfiguration,
|
|
||||||
eventData: {},
|
|
||||||
};
|
|
||||||
yield put(createOnboardingActionInit(actionPayload));
|
|
||||||
const response: ActionCreateUpdateResponse = yield ActionAPI.createAPI(
|
|
||||||
actionPayload,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isValidResponse = yield validateResponse(response);
|
|
||||||
if (isValidResponse) {
|
|
||||||
const newAction = {
|
|
||||||
...response.data,
|
|
||||||
datasource: onboardingDatasource,
|
|
||||||
};
|
|
||||||
yield put(expandDatasourceEntity(onboardingDatasource.id));
|
|
||||||
|
|
||||||
yield put(createOnboardingActionSuccess(newAction));
|
|
||||||
|
|
||||||
// Run query
|
|
||||||
const timeout = yield select(getActionTimeout, newAction.id);
|
|
||||||
const executeActionResponse: ActionApiResponse = yield ActionAPI.executeAction(
|
|
||||||
{
|
|
||||||
actionId: newAction.id,
|
|
||||||
viewMode: false,
|
|
||||||
},
|
|
||||||
timeout,
|
|
||||||
);
|
|
||||||
yield validateResponse(response);
|
|
||||||
const payload = {
|
|
||||||
...executeActionResponse.data,
|
|
||||||
...executeActionResponse.clientMeta,
|
|
||||||
};
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.RUN_ACTION_SUCCESS,
|
|
||||||
payload: { [newAction.id]: payload },
|
|
||||||
});
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.CREATE_ONBOARDING_DBQUERY_SUCCESS,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navigate to that datasource page
|
|
||||||
yield put(changeDatasource(onboardingDatasource));
|
|
||||||
|
|
||||||
yield put(showTooltip(OnboardingStep.EXAMPLE_DATABASE));
|
|
||||||
|
|
||||||
// Need to hide this tooltip based on some events
|
|
||||||
yield hideDatabaseTooltip();
|
|
||||||
} else {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionErrorTypes.CREATE_ONBOARDING_ACTION_ERROR,
|
|
||||||
payload: actionPayload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionErrorTypes.CREATE_ONBOARDING_DBQUERY_ERROR,
|
|
||||||
payload: { error },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* listenForWidgetUnselection() {
|
|
||||||
while (true) {
|
|
||||||
yield take();
|
|
||||||
|
|
||||||
yield take(ReduxActionTypes.HIDE_PROPERTY_PANE);
|
|
||||||
const currentStep = yield select(getCurrentStep);
|
|
||||||
const isinOnboarding = yield select(inOnboarding);
|
|
||||||
|
|
||||||
if (!isinOnboarding || currentStep !== OnboardingStep.SUCCESSFUL_BINDING)
|
|
||||||
return;
|
|
||||||
|
|
||||||
yield put(setCurrentStep(OnboardingStep.DEPLOY));
|
|
||||||
|
|
||||||
yield delay(1000);
|
|
||||||
yield put(showTooltip(OnboardingStep.DEPLOY));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* listenForDeploySaga() {
|
|
||||||
while (true) {
|
|
||||||
yield take();
|
|
||||||
|
|
||||||
yield take(ReduxActionTypes.PUBLISH_APPLICATION_SUCCESS);
|
|
||||||
yield put(showTooltip(OnboardingStep.NONE));
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.SHOW_ONBOARDING_COMPLETION_DIALOG,
|
|
||||||
payload: true,
|
|
||||||
});
|
|
||||||
yield put(setOnboardingReduxState(false));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* initiateOnboarding() {
|
|
||||||
const currentOnboardingState = yield getOnboardingState();
|
|
||||||
AnalyticsUtil.logEvent("ONBOARDING_WELCOME");
|
|
||||||
if (currentOnboardingState) {
|
|
||||||
yield put(setOnboardingReduxState(true));
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.NEXT_ONBOARDING_STEP,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* proceedOnboardingSaga() {
|
|
||||||
const isInOnboarding = yield select(inOnboarding);
|
|
||||||
|
|
||||||
if (isInOnboarding) {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.INCREMENT_STEP,
|
|
||||||
});
|
|
||||||
|
|
||||||
yield setupOnboardingStep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* setupOnboardingStep() {
|
|
||||||
const currentStep: OnboardingStep = yield select(getCurrentStep);
|
|
||||||
const currentConfig = OnboardingConfig[currentStep];
|
|
||||||
if (currentConfig.eventName) {
|
|
||||||
AnalyticsUtil.logEvent(currentConfig.eventName);
|
|
||||||
}
|
|
||||||
let actions = currentConfig.setup();
|
|
||||||
|
|
||||||
if (actions.length) {
|
|
||||||
actions = actions.map(action => put(action));
|
|
||||||
yield all(actions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* skipOnboardingSaga() {
|
|
||||||
AnalyticsUtil.logEvent("END_ONBOARDING");
|
|
||||||
const set = yield setOnboardingState(false);
|
|
||||||
|
|
||||||
if (set) {
|
|
||||||
yield put(setOnboardingReduxState(false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function* onboardingSagas() {
|
|
||||||
yield all([
|
|
||||||
takeEvery(ReduxActionTypes.CREATE_APPLICATION_SUCCESS, initiateOnboarding),
|
|
||||||
takeEvery(
|
|
||||||
ReduxActionTypes.CREATE_ONBOARDING_DBQUERY_INIT,
|
|
||||||
createOnboardingDatasource,
|
|
||||||
),
|
|
||||||
takeEvery(ReduxActionTypes.NEXT_ONBOARDING_STEP, proceedOnboardingSaga),
|
|
||||||
takeEvery(ReduxActionTypes.END_ONBOARDING, skipOnboardingSaga),
|
|
||||||
takeEvery(ReduxActionTypes.LISTEN_FOR_ADD_WIDGET, listenForWidgetAdditions),
|
|
||||||
takeEvery(
|
|
||||||
ReduxActionTypes.LISTEN_FOR_TABLE_WIDGET_BINDING,
|
|
||||||
listenForSuccessfullBinding,
|
|
||||||
),
|
|
||||||
takeEvery(
|
|
||||||
ReduxActionTypes.LISTEN_FOR_WIDGET_UNSELECTION,
|
|
||||||
listenForWidgetUnselection,
|
|
||||||
),
|
|
||||||
takeEvery(ReduxActionTypes.SET_CURRENT_STEP, setupOnboardingStep),
|
|
||||||
takeEvery(ReduxActionTypes.LISTEN_FOR_DEPLOY, listenForDeploySaga),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
@ -20,8 +20,6 @@ import modalSagas from "./ModalSagas";
|
||||||
import batchSagas from "./BatchSagas";
|
import batchSagas from "./BatchSagas";
|
||||||
import themeSagas from "./ThemeSaga";
|
import themeSagas from "./ThemeSaga";
|
||||||
import evaluationsSaga from "./evaluationsSaga";
|
import evaluationsSaga from "./evaluationsSaga";
|
||||||
import onboardingSaga from "./OnboardingSagas";
|
|
||||||
|
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import * as sentry from "@sentry/react";
|
import * as sentry from "@sentry/react";
|
||||||
|
|
||||||
|
|
@ -48,7 +46,6 @@ export function* rootSaga() {
|
||||||
batchSagas,
|
batchSagas,
|
||||||
themeSagas,
|
themeSagas,
|
||||||
evaluationsSaga,
|
evaluationsSaga,
|
||||||
onboardingSaga,
|
|
||||||
];
|
];
|
||||||
yield all(
|
yield all(
|
||||||
sagas.map(saga =>
|
sagas.map(saga =>
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,6 @@ import { CanvasWidgetsReduxState } from "../reducers/entityReducers/canvasWidget
|
||||||
export const getEntities = (state: AppState): AppState["entities"] =>
|
export const getEntities = (state: AppState): AppState["entities"] =>
|
||||||
state.entities;
|
state.entities;
|
||||||
|
|
||||||
export const getDatasources = (state: AppState): Datasource[] => {
|
|
||||||
return state.entities.datasources.list;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPluginIdsOfNames = (
|
export const getPluginIdsOfNames = (
|
||||||
state: AppState,
|
state: AppState,
|
||||||
names: Array<string>,
|
names: Array<string>,
|
||||||
|
|
|
||||||
|
|
@ -87,13 +87,7 @@ export type EventName =
|
||||||
| "ROUTE_CHANGE"
|
| "ROUTE_CHANGE"
|
||||||
| "PROPERTY_PANE_CLOSE_CLICK"
|
| "PROPERTY_PANE_CLOSE_CLICK"
|
||||||
| "APPLICATIONS_PAGE_LOAD"
|
| "APPLICATIONS_PAGE_LOAD"
|
||||||
| "EXECUTE_ACTION"
|
| "EXECUTE_ACTION";
|
||||||
| "ONBOARDING_WELCOME"
|
|
||||||
| "ONBOARDING_EXAMPLE_DATABASE"
|
|
||||||
| "ONBOARDING_ADD_WIDGET"
|
|
||||||
| "ONBOARDING_SUCCESSFUL_BINDING"
|
|
||||||
| "ONBOARDING_DEPLOY"
|
|
||||||
| "END_ONBOARDING";
|
|
||||||
|
|
||||||
function getApplicationId(location: Location) {
|
function getApplicationId(location: Location) {
|
||||||
const pathSplit = location.pathname.split("/");
|
const pathSplit = location.pathname.split("/");
|
||||||
|
|
@ -209,7 +203,6 @@ class AnalyticsUtil {
|
||||||
userData: user.userId === ANONYMOUS_USERNAME ? undefined : user,
|
userData: user.userId === ANONYMOUS_USERNAME ? undefined : user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (windowDoc.analytics) {
|
if (windowDoc.analytics) {
|
||||||
log.debug("Event fired", eventName, finalEventData);
|
log.debug("Event fired", eventName, finalEventData);
|
||||||
windowDoc.analytics.track(eventName, finalEventData);
|
windowDoc.analytics.track(eventName, finalEventData);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import { GridDefaults } from "constants/WidgetConstants";
|
import { GridDefaults } from "constants/WidgetConstants";
|
||||||
import lottie from "lottie-web";
|
|
||||||
import confetti from "assets/lottie/confetti.json";
|
|
||||||
import {
|
import {
|
||||||
DATA_TREE_KEYWORDS,
|
DATA_TREE_KEYWORDS,
|
||||||
JAVASCRIPT_KEYWORDS,
|
JAVASCRIPT_KEYWORDS,
|
||||||
|
|
@ -193,34 +191,3 @@ export const isNameValid = (
|
||||||
name in invalidNames
|
name in invalidNames
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const playOnboardingAnimation = () => {
|
|
||||||
const container: Element = document.getElementById("root") as Element;
|
|
||||||
|
|
||||||
const el = document.createElement("div");
|
|
||||||
Object.assign(el.style, {
|
|
||||||
position: "absolute",
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
"z-index": 99,
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
});
|
|
||||||
|
|
||||||
container.appendChild(el);
|
|
||||||
|
|
||||||
const animObj = lottie.loadAnimation({
|
|
||||||
container: el,
|
|
||||||
animationData: confetti,
|
|
||||||
loop: false,
|
|
||||||
});
|
|
||||||
const duration = (animObj.totalFrames / animObj.frameRate) * 1000;
|
|
||||||
|
|
||||||
animObj.play();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
container.removeChild(el);
|
|
||||||
}, duration);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ const STORAGE_KEYS: { [id: string]: string } = {
|
||||||
ROUTE_BEFORE_LOGIN: "RedirectPath",
|
ROUTE_BEFORE_LOGIN: "RedirectPath",
|
||||||
COPIED_WIDGET: "CopiedWidget",
|
COPIED_WIDGET: "CopiedWidget",
|
||||||
DELETED_WIDGET_PREFIX: "DeletedWidget-",
|
DELETED_WIDGET_PREFIX: "DeletedWidget-",
|
||||||
ONBOARDING_STATE: "OnboardingState",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const store = localforage.createInstance({
|
const store = localforage.createInstance({
|
||||||
|
|
@ -92,22 +91,3 @@ export const flushDeletedWidgets = async (widgetId: string) => {
|
||||||
console.log("An error occurred when flushing deleted widgets: ", error);
|
console.log("An error occurred when flushing deleted widgets: ", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setOnboardingState = async (onboardingState: boolean) => {
|
|
||||||
try {
|
|
||||||
await store.setItem(STORAGE_KEYS.ONBOARDING_STATE, onboardingState);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.log("An error occurred when setting onboarding state: ", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getOnboardingState = async () => {
|
|
||||||
try {
|
|
||||||
const onboardingState = await store.getItem(STORAGE_KEYS.ONBOARDING_STATE);
|
|
||||||
return onboardingState;
|
|
||||||
} catch (error) {
|
|
||||||
console.log("An error occurred when getting onboarding state: ", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -12492,11 +12492,6 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
|
||||||
dependencies:
|
dependencies:
|
||||||
js-tokens "^3.0.0 || ^4.0.0"
|
js-tokens "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
lottie-web@^5.7.4:
|
|
||||||
version "5.7.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.7.4.tgz#3b252148e904a0aa9833879ffb64924c85a0888c"
|
|
||||||
integrity sha512-LxqhXlHnHXOPmu+o2ipFKGv42jZLmn/GiEwXP0YC331fFwa+y96OUV22OF9r4i29uWKDciXiJr8tzy6jL8KygA==
|
|
||||||
|
|
||||||
loud-rejection@^1.0.0:
|
loud-rejection@^1.0.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user