mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 01:18:23 +00:00
Merge pull request #10214 from ToolJet/chore/main-to-develop
Merge main back to develop (v2.63.0)
This commit is contained in:
commit
9e7c0e6005
170 changed files with 45366 additions and 7832 deletions
|
|
@ -155,4 +155,4 @@ jobs:
|
|||
message="Job '${{ env.JOB_NAME }}' failed! tooljet/tooljet-ce:${{ github.event.inputs.image }}"
|
||||
fi
|
||||
|
||||
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
2.61.3
|
||||
2.63.0
|
||||
|
|
|
|||
|
|
@ -296,7 +296,6 @@ describe(
|
|||
});
|
||||
});
|
||||
|
||||
cy.get(usersSelector.acceptInvite).click();
|
||||
cy.get('[data-cy="draggable-widget-table1"]').should("be.visible");
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ describe("Password reset functionality", () => {
|
|||
);
|
||||
cy.get(commonSelectors.forgotPasswordPageSubHeader).should(($el) => {
|
||||
expect($el.contents().first().text().trim()).to.eq(
|
||||
commonText.newToTooljetText
|
||||
"New to"
|
||||
);
|
||||
});
|
||||
cy.get(commonSelectors.createAnAccountLink).verifyVisibleElement(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ describe("User signup", () => {
|
|||
it("Verify invalid invitation link", () => {
|
||||
cy.log(invitationLink)
|
||||
cy.visit(invitationLink);
|
||||
cy.pause()
|
||||
verifyInvalidInvitationLink();
|
||||
cy.get(commonSelectors.backtoSignUpButton).click();
|
||||
cy.get(commonSelectors.SignUpSectionHeader).should("be.visible");
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import { buttonText } from "Texts/button";
|
|||
|
||||
describe("App Export Functionality", () => {
|
||||
var data = {};
|
||||
data.appName1 = `${fake.companyName}-App`;
|
||||
let currentVersion = "";
|
||||
let otherVersions = [];
|
||||
beforeEach(() => {
|
||||
|
|
@ -29,6 +28,7 @@ describe("App Export Functionality", () => {
|
|||
});
|
||||
|
||||
it("Verify the elements of export dialog box", () => {
|
||||
data.appName1 = `${fake.companyName}-App`;
|
||||
cy.apiCreateApp(data.appName1);
|
||||
cy.openApp();
|
||||
cy.dragAndDropWidget(buttonText.defaultWidgetText);
|
||||
|
|
@ -56,6 +56,9 @@ describe("App Export Functionality", () => {
|
|||
});
|
||||
|
||||
it("Verify 'Export app' functionality of an application", () => {
|
||||
data.appName1 = `${fake.companyName}-App`;
|
||||
cy.apiCreateApp(data.appName1);
|
||||
|
||||
cy.visit("/");
|
||||
cy.get(commonSelectors.appHeaderLable).should("be.visible");
|
||||
|
||||
|
|
@ -96,7 +99,7 @@ describe("App Export Functionality", () => {
|
|||
.click();
|
||||
createNewVersion((otherVersions = ["v2"]), (currentVersion = "v1"));
|
||||
cy.wait(500);
|
||||
cy.dragAndDropWidget("Toggle Switch", 50, 50);
|
||||
cy.dragAndDropWidget("Text Input", 50, 50);
|
||||
cy.waitForAutoSave();
|
||||
cy.get(appVersionSelectors.currentVersionField((otherVersions = "v2")))
|
||||
.should("be.visible")
|
||||
|
|
@ -34,9 +34,9 @@ describe("Bulk user upload", () => {
|
|||
data.firstName = fake.firstName;
|
||||
data.workspaceName = data.firstName.toLowerCase();
|
||||
|
||||
cy.apiLogin()
|
||||
cy.apiLogin();
|
||||
cy.apiCreateWorkspace(data.firstName, data.workspaceName);
|
||||
cy.visit(`${data.workspaceName}`)
|
||||
cy.visit(`${data.workspaceName}`);
|
||||
|
||||
common.navigateToManageUsers();
|
||||
|
||||
|
|
@ -122,9 +122,9 @@ describe("Bulk user upload", () => {
|
|||
data.firstName = fake.firstName;
|
||||
data.workspaceName = data.firstName.toLowerCase();
|
||||
|
||||
cy.apiLogin()
|
||||
cy.apiLogin();
|
||||
cy.apiCreateWorkspace(data.firstName, data.workspaceName);
|
||||
cy.visit(`${data.workspaceName}`)
|
||||
cy.visit(`${data.workspaceName}`);
|
||||
common.navigateToManageUsers();
|
||||
|
||||
cy.get(usersSelector.buttonAddUsers).click();
|
||||
|
|
@ -142,7 +142,10 @@ describe("Bulk user upload", () => {
|
|||
force: true,
|
||||
});
|
||||
cy.get(usersSelector.buttonUploadUsers).click();
|
||||
cy.wait(10000);
|
||||
cy.wait(30000);
|
||||
cy.get(".go2072408551")
|
||||
.should("be.visible")
|
||||
.and("have.text", "250 users are being added");
|
||||
common.searchUser("test12@gmail.com");
|
||||
cy.contains("td", "test12@gmail.com")
|
||||
.parent()
|
||||
|
|
|
|||
|
|
@ -131,7 +131,8 @@ describe("user invite flow cases", () => {
|
|||
cy.apiLogin();
|
||||
cy.apiCreateWorkspace(data.workspaceName, data.workspaceName);
|
||||
cy.visit(`${data.workspaceName}`);
|
||||
enableSignUp();
|
||||
cy.wait(3000)
|
||||
setSignupStatus(true, data.workspaceName);
|
||||
logout();
|
||||
|
||||
cy.get(commonSelectors.createAnAccountLink).click();
|
||||
|
|
@ -142,11 +143,6 @@ describe("user invite flow cases", () => {
|
|||
|
||||
cy.defaultWorkspaceLogin();
|
||||
visitWorkspaceInvitation(data.email, data.workspaceName);
|
||||
|
||||
cy.clearAndType(commonSelectors.workEmailInputField, data.email);
|
||||
cy.clearAndType(commonSelectors.passwordInputField, "password");
|
||||
cy.get(commonSelectors.signInButton).click();
|
||||
cy.get(usersSelector.acceptInvite).click();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.inviteToast);
|
||||
logout();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -552,21 +552,25 @@ export const defaultSSO = (enable) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const setSignupStatus = (enable) => {
|
||||
cy.getCookie("tj_auth_token").then((cookie) => {
|
||||
cy.request(
|
||||
{
|
||||
export const setSignupStatus = (enable, workspaceName = 'My workspace') => {
|
||||
cy.task("updateId", {
|
||||
dbconfig: Cypress.env("app_db"),
|
||||
sql: `SELECT id FROM organizations WHERE name = '${workspaceName}'`,
|
||||
}).then((resp) => {
|
||||
const workspaceId = resp.rows[0].id;
|
||||
|
||||
cy.getCookie("tj_auth_token").then((cookie) => {
|
||||
cy.request({
|
||||
method: "PATCH",
|
||||
url: "http://localhost:3000/api/organizations",
|
||||
headers: {
|
||||
"Tj-Workspace-Id": Cypress.env("workspaceId"),
|
||||
"Tj-Workspace-Id": workspaceId,
|
||||
Cookie: `tj_auth_token=${cookie.value}`,
|
||||
},
|
||||
body: { enableSignUp: enable },
|
||||
},
|
||||
{ log: false }
|
||||
).then((response) => {
|
||||
expect(response.status).to.equal(200);
|
||||
}).then((response) => {
|
||||
expect(response.status).to.equal(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
display: flex;
|
||||
padding: 1.5rem;
|
||||
flex: 1 1 auto;
|
||||
align-items: start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.card-info {
|
||||
|
|
|
|||
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
|
|
@ -21,3 +21,5 @@
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
storybook-static
|
||||
14
frontend/.storybook/decorators.jsx
Normal file
14
frontend/.storybook/decorators.jsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// storybookDecorators.js
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export function withColorScheme(story, context) {
|
||||
const darkMode = context?.globals?.backgrounds?.value === '#333333'; // Access theme mode from globals
|
||||
const className = darkMode ? 'dark-theme' : '';
|
||||
|
||||
return (
|
||||
<div className={className} style={{ backgroundColor: 'transparent' }}>
|
||||
{story()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
/** @type { import('@storybook/react-webpack5').StorybookConfig } */
|
||||
import custom from '../webpack.config'
|
||||
const path = require('path');
|
||||
import customWebpackConfig from '../webpack.config';
|
||||
import path from 'path';
|
||||
|
||||
const config = {
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
|
|
@ -17,16 +16,19 @@ const config = {
|
|||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
webpackFinal: async (config) => {
|
||||
webpackFinal: async (storybookConfig) => {
|
||||
return {
|
||||
...config,
|
||||
module: { ...config.module, rules: [...config.module.rules, ...custom.module.rules] },
|
||||
resolve : {
|
||||
alias : {
|
||||
'@': path.resolve(__dirname, 'src/')
|
||||
...storybookConfig,
|
||||
module: { ...storybookConfig.module, rules: [...storybookConfig.module.rules, ...customWebpackConfig.module.rules] },
|
||||
resolve: {
|
||||
...storybookConfig.resolve,
|
||||
alias: {
|
||||
...storybookConfig.resolve.alias,
|
||||
'@': path.resolve(__dirname, '../src/')
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/** @type { import('@storybook/react').Preview } */
|
||||
// import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
import '../src/_styles/theme.scss';
|
||||
import './preview.scss'
|
||||
import './preview.scss';
|
||||
import { withColorScheme } from './decorators'; // Import the decorator
|
||||
|
||||
const preview = {
|
||||
parameters: {
|
||||
|
|
@ -13,6 +14,7 @@ const preview = {
|
|||
},
|
||||
},
|
||||
},
|
||||
decorators: [withColorScheme], // Adding the decorator to the decorators array
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
@import '~bootstrap/scss/bootstrap';
|
||||
|
||||
@import '../src/_styles/componentdesign.scss'
|
||||
|
|
@ -1 +1 @@
|
|||
2.61.3
|
||||
2.63.0
|
||||
|
|
|
|||
131
frontend/ce/white-label/whiteLabelling.js
Normal file
131
frontend/ce/white-label/whiteLabelling.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { decodeEntities } from '@/_helpers/utils';
|
||||
|
||||
export const defaultWhiteLabellingSettings = {
|
||||
WHITE_LABEL_LOGO: 'assets/images/rocket.svg',
|
||||
WHITE_LABEL_TEXT: 'ToolJet',
|
||||
WHITE_LABEL_FAVICON: 'assets/images/logo.svg',
|
||||
};
|
||||
|
||||
export const whiteLabellingOptions = {
|
||||
WHITE_LABEL_LOGO: 'App Logo',
|
||||
WHITE_LABEL_TEXT: 'Page Title',
|
||||
WHITE_LABEL_FAVICON: 'Favicon',
|
||||
};
|
||||
|
||||
export async function fetchWhiteLabelDetails() {}
|
||||
|
||||
export async function checkWhiteLabelsDefaultState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function resetToDefaultWhiteLabels() {}
|
||||
|
||||
export function retrieveWhiteLabelText() {
|
||||
return window.public_config?.WHITE_LABEL_TEXT || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT;
|
||||
}
|
||||
|
||||
export function retrieveWhiteLabelLogo() {
|
||||
return window.public_config?.WHITE_LABEL_LOGO || defaultWhiteLabellingSettings.WHITE_LABEL_LOGO;
|
||||
}
|
||||
|
||||
export function retrieveWhiteLabelFavicon() {
|
||||
return window.public_config?.WHITE_LABEL_FAVICON || defaultWhiteLabellingSettings.WHITE_LABEL_FAVICON;
|
||||
}
|
||||
|
||||
export const pageTitles = {
|
||||
INSTANCE_SETTINGS: 'Settings',
|
||||
WORKSPACE_SETTINGS: 'Workspace settings',
|
||||
INTEGRATIONS: 'Marketplace',
|
||||
WORKFLOWS: 'Workflows',
|
||||
DATABASE: 'Database',
|
||||
DATA_SOURCES: 'Data sources',
|
||||
AUDIT_LOGS: 'Audit logs',
|
||||
ACCOUNT_SETTINGS: 'Profile settings',
|
||||
SETTINGS: 'Profile settings',
|
||||
EDITOR: 'Editor',
|
||||
WORKFLOW_EDITOR: 'workflowEditor',
|
||||
VIEWER: 'Viewer',
|
||||
DASHBOARD: 'Dashboard',
|
||||
WORKSPACE_CONSTANTS: 'Workspace constants',
|
||||
};
|
||||
|
||||
// to set favicon and title from router for individual pages
|
||||
export async function setFaviconAndTitle(whiteLabelFavicon, whiteLabelText, location) {
|
||||
if (!whiteLabelFavicon || !whiteLabelText) {
|
||||
whiteLabelFavicon = await retrieveWhiteLabelFavicon();
|
||||
whiteLabelText = await retrieveWhiteLabelText();
|
||||
}
|
||||
// Set favicon
|
||||
let links = document.querySelectorAll("link[rel='icon']");
|
||||
if (links.length === 0) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
link.type = 'image/svg+xml';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
links = [link];
|
||||
}
|
||||
links.forEach((link) => {
|
||||
link.href = `${whiteLabelFavicon || defaultWhiteLabellingSettings.WHITE_LABEL_FAVICON}`;
|
||||
});
|
||||
// Set title
|
||||
const isEditorOrViewerGoingToRender = ['/apps/', '/applications/'].some((path) => location?.pathname.includes(path));
|
||||
if (isEditorOrViewerGoingToRender) {
|
||||
return;
|
||||
}
|
||||
const pathToTitle = {
|
||||
'instance-settings': pageTitles.INSTANCE_SETTINGS,
|
||||
'workspace-settings': pageTitles.WORKSPACE_SETTINGS,
|
||||
integrations: pageTitles.INTEGRATIONS,
|
||||
workflows: pageTitles.WORKFLOWS,
|
||||
'data-sources': pageTitles.DATA_SOURCES,
|
||||
'audit-logs': pageTitles.AUDIT_LOGS,
|
||||
'account-settings': pageTitles.ACCOUNT_SETTINGS,
|
||||
settings: pageTitles.INSTANCE_SETTINGS,
|
||||
login: '',
|
||||
signUp: '',
|
||||
error: '',
|
||||
signup: '',
|
||||
'organization-invitations': '',
|
||||
invitation: '',
|
||||
'forgot-password': '',
|
||||
'reset-password': '',
|
||||
'workspace-constants': pageTitles.WORKSPACE_CONSTANTS,
|
||||
setup: '',
|
||||
};
|
||||
const pageTitleKey = Object.keys(pathToTitle).find((path) => location?.pathname.includes(path));
|
||||
const pageTitle = pathToTitle[pageTitleKey];
|
||||
|
||||
//For undefined routes
|
||||
if (pageTitle === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageTitleKey && !isEditorOrViewerGoingToRender) {
|
||||
document.title = pageTitle
|
||||
? `${decodeEntities(pageTitle)} | ${whiteLabelText || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT}`
|
||||
: `${decodeEntities(whiteLabelText) || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAndSetWindowTitle(pageDetails) {
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
let pageTitleKey = pageDetails?.page || '';
|
||||
let pageTitle = '';
|
||||
switch (pageTitleKey) {
|
||||
case pageTitles.VIEWER: {
|
||||
const titlePrefix = pageDetails?.preview ? 'Preview - ' : '';
|
||||
pageTitle = `${titlePrefix}${pageDetails?.appName || 'My App'}`;
|
||||
break;
|
||||
}
|
||||
case pageTitles.EDITOR:
|
||||
case pageTitles.WORKFLOW_EDITOR: {
|
||||
pageTitle = pageDetails?.appName || 'My App';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pageTitle = pageTitleKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.title = !(pageDetails?.preview === false) ? `${pageTitle} | ${whiteLabelText}` : `${pageTitle}`;
|
||||
}
|
||||
17
frontend/components.json
Normal file
17
frontend/components.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": false,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/styles/theme.scss",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
9
frontend/netlify.toml
Normal file
9
frontend/netlify.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[build]
|
||||
base = "frontend/"
|
||||
publish = "storybook-static"
|
||||
command = "npx storybook build"
|
||||
|
||||
[template.environment]
|
||||
NODE_ENV = "production"
|
||||
NODE_VERSION = "18.18.2"
|
||||
NPM_VERSION = "9.8.1"
|
||||
30523
frontend/package-lock.json
generated
30523
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -19,6 +19,7 @@
|
|||
"@radix-ui/colors": "^0.1.8",
|
||||
"@radix-ui/react-popover": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@react-google-maps/api": "^2.18.1",
|
||||
"@sentry/react": "^7.100.1",
|
||||
|
|
@ -35,6 +36,7 @@
|
|||
"acorn": "^8.11.3",
|
||||
"axios": "^1.3.3",
|
||||
"bootstrap": "^5.2.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"classnames": "^2.3.2",
|
||||
"deep-object-diff": "^1.1.9",
|
||||
"dompurify": "^3.0.0",
|
||||
|
|
@ -106,10 +108,13 @@
|
|||
"react-tooltip": "^5.8.1",
|
||||
"react-virtuoso": "^4.1.0",
|
||||
"react-zoom-pan-pinch": "^2.6.1",
|
||||
"rfdc": "^1.3.1",
|
||||
"rxjs": "^7.8.0",
|
||||
"semver": "^7.3.8",
|
||||
"string-hash": "^1.1.3",
|
||||
"superstruct": "^1.0.3",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"url-join": "^5.0.0",
|
||||
"use-react-router-breadcrumbs": "^4.0.1",
|
||||
|
|
@ -137,6 +142,7 @@
|
|||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-console-source": "^2.0.5",
|
||||
"babel-plugin-import": "^1.13.6",
|
||||
|
|
@ -158,10 +164,13 @@
|
|||
"jest": "^29.4.2",
|
||||
"node-sass": "^8.0.0",
|
||||
"path": "^0.12.7",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-loader": "^8.1.0",
|
||||
"prettier": "^2.8.4",
|
||||
"sass-loader": "^13.2.0",
|
||||
"storybook": "^7.2.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
|
|
@ -192,7 +201,7 @@
|
|||
"format": "eslint . --fix '**/*.{js,jsx}'",
|
||||
"test": "jest",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
"build-storybook": "npx storybook build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
|
|
|
|||
8
frontend/postcss.config.js
Normal file
8
frontend/postcss.config.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// postcss.config.js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
// Other PostCSS plugins if needed
|
||||
],
|
||||
};
|
||||
|
|
@ -39,6 +39,7 @@ import { ManageOrgUsers } from '@/ManageOrgUsers';
|
|||
import { ManageGroupPermissions } from '@/ManageGroupPermissions';
|
||||
import OrganizationLogin from '@/_components/OrganizationLogin/OrganizationLogin';
|
||||
import { ManageOrgVars } from '@/ManageOrgVars';
|
||||
import { setFaviconAndTitle } from '@white-label/whiteLabelling';
|
||||
|
||||
const AppWrapper = (props) => {
|
||||
const { isAppDarkMode } = useAppDarkMode();
|
||||
|
|
@ -78,6 +79,7 @@ class AppComponent extends React.Component {
|
|||
componentDidMount() {
|
||||
authorizeWorkspace();
|
||||
this.fetchMetadata();
|
||||
setFaviconAndTitle(null, null, this.props.location);
|
||||
setInterval(this.fetchMetadata, 1000 * 60 * 60 * 1);
|
||||
}
|
||||
// check if its getting routed from editor
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ import Spinner from '@/_ui/Spinner';
|
|||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import { onLoginSuccess } from '@/_helpers/platform/utils/auth.utils';
|
||||
import { updateCurrentSession } from '@/_helpers/authorizeWorkspace';
|
||||
import {
|
||||
retrieveWhiteLabelText,
|
||||
setFaviconAndTitle,
|
||||
retrieveWhiteLabelFavicon,
|
||||
checkWhiteLabelsDefaultState,
|
||||
} from '@white-label/whiteLabelling';
|
||||
class OrganizationInvitationPageComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -21,10 +27,18 @@ class OrganizationInvitationPageComponent extends React.Component {
|
|||
this.organizationId = new URLSearchParams(props?.location?.search).get('oid');
|
||||
this.organizationToken = new URLSearchParams(props?.location?.search).get('organizationToken');
|
||||
this.source = new URLSearchParams(props?.location?.search).get('source');
|
||||
this.whiteLabelText = retrieveWhiteLabelText();
|
||||
this.whiteLabelFavicon = retrieveWhiteLabelFavicon();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
authenticationService.deleteLoginOrganizationId();
|
||||
setFaviconAndTitle(this.whiteLabelText, this.whiteLabelFavicon, this.props?.location);
|
||||
checkWhiteLabelsDefaultState(this.organizationId).then((res) => {
|
||||
this.setState({ defaultState: res });
|
||||
this.whiteLabelText = retrieveWhiteLabelText();
|
||||
this.whiteLabelFavicon = retrieveWhiteLabelFavicon();
|
||||
});
|
||||
document.addEventListener('keydown', this.handleEnterKey);
|
||||
}
|
||||
|
||||
|
|
@ -74,14 +88,14 @@ class OrganizationInvitationPageComponent extends React.Component {
|
|||
<form action="." method="get" autoComplete="off">
|
||||
<div className="common-auth-container-wrapper">
|
||||
<h2 className="common-auth-section-header org-invite-header" data-cy="invite-page-header">
|
||||
Join {organizationName ? organizationName : 'ToolJet'}
|
||||
Join {organizationName ? organizationName : this.whiteLabelText}
|
||||
</h2>
|
||||
|
||||
<div className="invite-sub-header" data-cy="invite-page-sub-header">
|
||||
{`You are invited to ${
|
||||
organizationName
|
||||
? `a workspace ${organizationName}. Accept the invite to join the workspace.`
|
||||
: 'ToolJet.'
|
||||
: this.whiteLabelText
|
||||
}`}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,34 +2,15 @@ import React from 'react';
|
|||
import HydrateWithResolveReferences from './Middlewares/HydrateWithResolveReferences';
|
||||
import BoxUI from './BoxUI';
|
||||
import _ from 'lodash';
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
function deepEqualityCheckusingLoDash(obj1, obj2) {
|
||||
return _.isEqual(obj1, obj2);
|
||||
}
|
||||
import { shouldUpdate } from './ControlledComponentToRender';
|
||||
|
||||
export const shouldUpdate = (prevProps, nextProps) => {
|
||||
return (
|
||||
deepEqualityCheckusingLoDash(prevProps?.id, nextProps?.id) &&
|
||||
deepEqualityCheckusingLoDash(prevProps?.component?.definition, nextProps?.component?.definition) &&
|
||||
prevProps?.width === nextProps?.width &&
|
||||
prevProps?.height === nextProps?.height
|
||||
);
|
||||
};
|
||||
|
||||
export const Box = (props) => {
|
||||
export const Box = React.memo((props) => {
|
||||
const { id, component, mode, customResolvables } = props;
|
||||
|
||||
/**
|
||||
* !This component does not consume the value returned from the below hook.
|
||||
* Only purpose of the hook is to force one rerender the component
|
||||
* */
|
||||
useEditorStore((state) => state.componentsNeedsUpdateOnNextRender.find((compId) => compId === id), shallow);
|
||||
|
||||
return (
|
||||
<HydrateWithResolveReferences id={id} mode={mode} component={component} customResolvables={customResolvables}>
|
||||
<BoxUI {...props} />
|
||||
</HydrateWithResolveReferences>
|
||||
);
|
||||
};
|
||||
}, shouldUpdate);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import _ from 'lodash';
|
||||
import { copilotService } from '@/_services/copilot.service';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export async function getRecommendation(currentContext, query, lang = 'javascript') {
|
||||
const words = query.split(' ');
|
||||
|
|
@ -66,7 +67,7 @@ function getResult(suggestionList, query) {
|
|||
}
|
||||
|
||||
export function getSuggestionKeys(refState) {
|
||||
const state = _.cloneDeep(refState);
|
||||
const state = deepClone(refState);
|
||||
const queries = state['queries'];
|
||||
const actions = [
|
||||
'runQuery',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable import/no-unresolved */
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
|
|
@ -16,6 +16,7 @@ import CodeHinter from './CodeHinter';
|
|||
import { CodeHinterContext } from '../CodeBuilder/CodeHinterContext';
|
||||
import { createReferencesLookup } from '@/_stores/utils';
|
||||
import { PreviewBox } from './PreviewBox';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const langSupport = Object.freeze({
|
||||
javascript: javascript(),
|
||||
|
|
@ -44,38 +45,22 @@ const MultiLineCodeEditor = (props) => {
|
|||
delayOnChange = true, // Added this prop to immediately update the onBlurUpdate callback
|
||||
} = props;
|
||||
|
||||
const [currentValue, setCurrentValue] = React.useState(() => initialValue);
|
||||
|
||||
const context = useContext(CodeHinterContext);
|
||||
|
||||
const { suggestionList } = createReferencesLookup(context, true);
|
||||
|
||||
const diffOfCurrentValue = React.useRef(null);
|
||||
const currentValueRef = useRef(initialValue);
|
||||
|
||||
const handleChange = React.useCallback((val) => {
|
||||
setCurrentValue(val);
|
||||
|
||||
const diff = val.length - currentValue.length;
|
||||
|
||||
if (diff > 0) {
|
||||
diffOfCurrentValue.current = val.slice(-diff);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const handleChange = (val) => (currentValueRef.current = val);
|
||||
|
||||
const handleOnBlur = () => {
|
||||
if (!delayOnChange) return onChange(currentValue);
|
||||
if (!delayOnChange) return onChange(currentValueRef.current);
|
||||
setTimeout(() => {
|
||||
onChange(currentValue);
|
||||
onChange(currentValueRef.current);
|
||||
}, 100);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentValue(initialValue);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [lang]);
|
||||
|
||||
const heightInPx = typeof height === 'string' && height?.includes('px') ? height : `${height}px`;
|
||||
|
||||
const theme = darkMode ? okaidia : githubLight;
|
||||
|
|
@ -221,7 +206,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
<ErrorBoundary>
|
||||
<div className="codehinter-container w-100 " data-cy={`${cyLabel}-input-field`} style={{ height: '100%' }}>
|
||||
<CodeMirror
|
||||
value={currentValue}
|
||||
value={initialValue}
|
||||
placeholder={placeholder}
|
||||
height={'100%'}
|
||||
minHeight={heightInPx}
|
||||
|
|
@ -257,7 +242,7 @@ const MultiLineCodeEditor = (props) => {
|
|||
{showPreview && (
|
||||
<div className="multiline-previewbox-wrapper">
|
||||
<PreviewBox
|
||||
currentValue={currentValue}
|
||||
currentValue={currentValueRef.current}
|
||||
validationSchema={null}
|
||||
setErrorStateActive={() => null}
|
||||
componentId={null}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ export const PreviewBox = ({
|
|||
|
||||
useEffect(() => {
|
||||
const [valid, _error, newValue, resolvedValue] = resolveReferences(currentValue, validationSchema, customVariables);
|
||||
|
||||
if (isWorkspaceVariable || !validationSchema || isEmpty(validationSchema)) {
|
||||
return setResolvedValue(newValue);
|
||||
}
|
||||
|
|
@ -364,6 +365,7 @@ const PreviewCodeBlock = ({ code, isExpectValue = false }) => {
|
|||
rootName={false}
|
||||
theme={darkMode ? 'dark' : 'light'}
|
||||
groupArraysAfterLength={hasDeepChild ? 10 : 100}
|
||||
maxDisplayLength={hasDeepChild ? 10 : 50}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
display: flex !important;
|
||||
align-items: start !important;
|
||||
align-items: flex-start !important;
|
||||
justify-content: space-between !important;
|
||||
padding: 0.35rem !important;
|
||||
font-size: 11px !important;
|
||||
|
|
@ -533,9 +533,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.disabled-cursor{
|
||||
.disabled-cursor {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.disabled-pointerevents{
|
||||
|
||||
.disabled-pointerevents {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
@ -4,7 +4,8 @@ import _, { isEmpty } from 'lodash';
|
|||
import { useCurrentStateStore } from '@/_stores/currentStateStore';
|
||||
import { any } from 'superstruct';
|
||||
import { generateSchemaFromValidationDefinition, validate } from '../component-properties-validation';
|
||||
import { hasCircularDependency, resolveReferences as olderResolverMethod } from '@/_helpers/utils';
|
||||
import { hasCircularDependency } from '@/_helpers/utils';
|
||||
import { validateMultilineCode } from '@/_helpers/utility';
|
||||
|
||||
const acorn = require('acorn');
|
||||
|
||||
|
|
@ -240,6 +241,16 @@ export const resolveReferences = (query, validationSchema, customResolvers = {})
|
|||
return resolveWorkspaceVariables(query);
|
||||
}
|
||||
|
||||
if (query?.startsWith('{{') && query?.endsWith('}}')) {
|
||||
const { status, data } = validateMultilineCode(query);
|
||||
|
||||
if (status === 'failed') {
|
||||
const errMessage = `${data.message} - ${data.description}`;
|
||||
|
||||
return [false, errMessage, query, query];
|
||||
}
|
||||
}
|
||||
|
||||
if ((!validationSchema || isEmpty(validationSchema)) && (!query?.includes('{{') || !query?.includes('}}'))) {
|
||||
return [true, error, resolvedValue];
|
||||
}
|
||||
|
|
@ -256,17 +267,10 @@ export const resolveReferences = (query, validationSchema, customResolvers = {})
|
|||
useJSResolvers = true;
|
||||
}
|
||||
|
||||
const customWidgetResolvers = ['listItem'];
|
||||
const isCustomResolvers = customWidgetResolvers.some((resolver) => query.includes(resolver));
|
||||
|
||||
const { lookupTable } = useResolveStore.getState();
|
||||
|
||||
if (useJSResolvers) {
|
||||
resolvedValue = resolveMultiDynamicReferences(query, lookupTable, queryHasJSCode);
|
||||
} else if (isCustomResolvers && !_.isEmpty(customResolvers)) {
|
||||
const currentState = useCurrentStateStore.getState();
|
||||
const resolvedCode = olderResolverMethod(query, currentState, '', customResolvers);
|
||||
resolvedValue = resolvedCode;
|
||||
} else {
|
||||
let value = query?.replace(/{{|}}/g, '').trim();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import Plotly from 'plotly.js-dist-min';
|
|||
import createPlotlyComponent from 'react-plotly.js/factory';
|
||||
import { isJson } from '@/_helpers/utils';
|
||||
const Plot = createPlotlyComponent(Plotly);
|
||||
import { isEqual, cloneDeep } from 'lodash';
|
||||
import { isEqual } from 'lodash';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
var tinycolor = require('tinycolor2');
|
||||
|
||||
export const Chart = function Chart({
|
||||
|
|
@ -232,7 +233,7 @@ const PlotComponent = memo(
|
|||
return (
|
||||
<Plot
|
||||
data={data}
|
||||
layout={cloneDeep(layout)} // Cloning the layout since the object is getting mutated inside the package
|
||||
layout={deepClone(layout)} // Cloning the layout since the object is getting mutated inside the package
|
||||
config={config}
|
||||
onClick={(e) => {
|
||||
onClick(e.points);
|
||||
|
|
|
|||
|
|
@ -247,8 +247,6 @@ export const FilePicker = ({
|
|||
}
|
||||
});
|
||||
});
|
||||
setSelectedFiles(fileData);
|
||||
onComponentOptionChanged(component, 'file', fileData, id);
|
||||
|
||||
onEvent('onFileSelected', filePickerEvents, { component })
|
||||
.then(() => {
|
||||
|
|
@ -263,7 +261,11 @@ export const FilePicker = ({
|
|||
}, 600);
|
||||
});
|
||||
})
|
||||
.then(() => onEvent('onFileLoaded', filePickerEvents, { component }));
|
||||
.then(() => onEvent('onFileLoaded', filePickerEvents, { component }))
|
||||
.then(() => {
|
||||
setSelectedFiles(fileData);
|
||||
onComponentOptionChanged(component, 'file', fileData, id);
|
||||
});
|
||||
}
|
||||
|
||||
if (fileRejections.length > 0) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { SubCustomDragLayer } from '@/Editor/SubCustomDragLayer';
|
|||
import { SubContainer } from '@/Editor/SubContainer';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
import _, { omit } from 'lodash';
|
||||
import _, { debounce, omit } from 'lodash';
|
||||
import { Box } from '@/Editor/Box';
|
||||
import { generateUIComponents } from './FormUtils';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
|
|
@ -14,6 +14,7 @@ import {
|
|||
removeFunctionObjects,
|
||||
} from '@/_helpers/appUtils';
|
||||
import { useAppInfo } from '@/_stores/appDataStore';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
export const Form = function Form(props) {
|
||||
const {
|
||||
id,
|
||||
|
|
@ -150,7 +151,7 @@ export const Form = function Form(props) {
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
Object.entries(formattedChildData).map(([key, { formKey, ...rest }]) => [key, rest]) // removing formkey from final exposed data
|
||||
);
|
||||
const formattedChildDataClone = _.cloneDeep(formattedChildData);
|
||||
const formattedChildDataClone = deepClone(formattedChildData);
|
||||
const exposedVariables = {
|
||||
...(!advanced && { children: formattedChildDataClone }),
|
||||
data: removeFunctionObjects(formattedChildData),
|
||||
|
|
@ -188,7 +189,9 @@ export const Form = function Form(props) {
|
|||
};
|
||||
const fireSubmissionEvent = () => {
|
||||
if (isValid) {
|
||||
onEvent('onSubmit', formEvents).then(() => resetComponent());
|
||||
onEvent('onSubmit', formEvents).then(() => {
|
||||
debounce(() => resetComponent(), 100)();
|
||||
});
|
||||
} else {
|
||||
fireEvent('onInvalid');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import _ from 'lodash';
|
|||
import React from 'react';
|
||||
import Board from './Board';
|
||||
import { isCardColoumnIdUpdated, updateCardData, updateColumnData, getData, isArray, isValidCardData } from './utils';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
|
||||
export const BoardContext = React.createContext({});
|
||||
|
||||
|
|
@ -19,7 +18,6 @@ export const KanbanBoard = ({
|
|||
dataCy,
|
||||
}) => {
|
||||
const { columns, cardData, enableAddCard } = properties;
|
||||
const currentState = useCurrentState();
|
||||
const { visibility, disabledState, width, minWidth, accentColor } = styles;
|
||||
|
||||
const [rawColumnData, setRawColumnData] = React.useState([]);
|
||||
|
|
@ -109,9 +107,7 @@ export const KanbanBoard = ({
|
|||
}
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
return (
|
||||
<BoardContext.Provider
|
||||
value={{ id, currentState, enableAddCard, accentColor, containerProps, removeComponent, darkMode }}
|
||||
>
|
||||
<BoardContext.Provider value={{ id, enableAddCard, accentColor, containerProps, removeComponent, darkMode }}>
|
||||
<div
|
||||
id={id}
|
||||
style={{ display: visibility ? '' : 'none' }}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { SubContainer } from '../SubContainer';
|
|||
import { Pagination } from '@/_components/Pagination';
|
||||
import { removeFunctionObjects } from '@/_helpers/appUtils';
|
||||
import _ from 'lodash';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export const Listview = function Listview({
|
||||
id,
|
||||
|
|
@ -79,7 +80,7 @@ export const Listview = function Listview({
|
|||
}, [columns]);
|
||||
|
||||
useEffect(() => {
|
||||
const childrenDataClone = _.cloneDeep(childrenData);
|
||||
const childrenDataClone = deepClone(childrenData);
|
||||
const exposedVariables = {
|
||||
data: removeFunctionObjects(childrenDataClone),
|
||||
children: childrenData,
|
||||
|
|
@ -103,7 +104,7 @@ export const Listview = function Listview({
|
|||
const componentNamesSet = new Set(
|
||||
Object.values(childComponents ?? {}).map((component) => component.component.name)
|
||||
);
|
||||
const filteredData = _.cloneDeep(childrenData);
|
||||
const filteredData = deepClone(childrenData);
|
||||
if (filteredData?.[0]) {
|
||||
Object.keys(filteredData?.[0]).forEach((item) => {
|
||||
if (!componentNamesSet?.has(item)) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Tooltip } from 'react-tooltip';
|
|||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import cx from 'classnames';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export function AddNewRowComponent({
|
||||
hideAddNewRowPopup,
|
||||
|
|
@ -152,7 +153,7 @@ export function AddNewRowComponent({
|
|||
<button
|
||||
className="btn btn-light btn-sm m-2"
|
||||
onClick={() => {
|
||||
const rowData = _.cloneDeep(newRowsState);
|
||||
const rowData = deepClone(newRowsState);
|
||||
const index = rowData.length;
|
||||
let newRow = getNewRowObject();
|
||||
newRow = utilityForNestedNewRow(newRow);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export const GlobalFilter = ({
|
|||
onClick={() => {
|
||||
setGlobalFilter(undefined);
|
||||
setValue('');
|
||||
debouncedChange('');
|
||||
onComponentOptionChanged(component, 'searchText', '');
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ import { OverlayTriggerComponent } from './OverlayTriggerComponent';
|
|||
import { diff } from 'deep-object-diff';
|
||||
import { isRowInValid } from '../tableUtils';
|
||||
import moment from 'moment';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
// utilityForNestedNewRow function is used to construct nested object while adding or updating new row when '.' is present in column key for adding new row
|
||||
const utilityForNestedNewRow = (row) => {
|
||||
|
|
@ -249,7 +250,7 @@ export function Table({
|
|||
setIsCellValueChanged(true);
|
||||
|
||||
const dataUpdates = tableDetails.dataUpdates || [];
|
||||
const clonedTableData = _.cloneDeep(tableData);
|
||||
const clonedTableData = deepClone(tableData);
|
||||
|
||||
let obj = changeSet ? changeSet[index] || {} : {};
|
||||
obj = _.set(obj, key, value);
|
||||
|
|
@ -281,7 +282,7 @@ export function Table({
|
|||
|
||||
const copyOfTableDetails = useRef(tableDetails);
|
||||
useEffect(() => {
|
||||
copyOfTableDetails.current = _.cloneDeep(tableDetails);
|
||||
copyOfTableDetails.current = deepClone(tableDetails);
|
||||
}, [JSON.stringify(tableDetails)]);
|
||||
|
||||
function handleNewRowCellValueChange(index, key, value, rowData) {
|
||||
|
|
@ -373,13 +374,13 @@ export function Table({
|
|||
}
|
||||
|
||||
function handleChangesSaved() {
|
||||
const clonedTableData = _.cloneDeep(tableData);
|
||||
const clonedTableData = deepClone(tableData);
|
||||
Object.keys(changeSet).forEach((key) => {
|
||||
clonedTableData[key] = {
|
||||
..._.merge(clonedTableData[key], changeSet[key]),
|
||||
};
|
||||
});
|
||||
updatedDataReference.current = _.cloneDeep(clonedTableData);
|
||||
updatedDataReference.current = deepClone(clonedTableData);
|
||||
|
||||
setExposedVariables({
|
||||
changeSet: {},
|
||||
|
|
@ -454,7 +455,7 @@ export function Table({
|
|||
columnData = useMemo(
|
||||
() =>
|
||||
columnData.filter((column) => {
|
||||
if (resolveReferences(column?.columnVisibility, currentState)) {
|
||||
if (resolveReferences(column?.columnVisibility)) {
|
||||
return column;
|
||||
}
|
||||
}),
|
||||
|
|
@ -478,7 +479,7 @@ export function Table({
|
|||
// Single-level nested property
|
||||
const [nestedKey, subKey] = nestedKeys;
|
||||
const nestedObject = transformedObject?.[nestedKey] || { ...row[nestedKey] }; // Retain existing nested object
|
||||
const newValue = resolveReferences(transformation, currentState, row[key], {
|
||||
const newValue = resolveReferences(transformation, row[key], {
|
||||
cellValue: row?.[nestedKey]?.[subKey],
|
||||
rowData: row,
|
||||
});
|
||||
|
|
@ -490,7 +491,7 @@ export function Table({
|
|||
transformedObject[nestedKey] = nestedObject;
|
||||
} else {
|
||||
// Non-nested property
|
||||
transformedObject[key] = resolveReferences(transformation, currentState, row[key], {
|
||||
transformedObject[key] = resolveReferences(transformation, row[key], {
|
||||
cellValue: row[key],
|
||||
rowData: row,
|
||||
});
|
||||
|
|
@ -1283,7 +1284,7 @@ export function Table({
|
|||
},
|
||||
};
|
||||
}
|
||||
const isEditable = resolveReferences(column?.isEditable ?? false, currentState);
|
||||
const isEditable = resolveReferences(column?.isEditable ?? false);
|
||||
return (
|
||||
<th
|
||||
key={index}
|
||||
|
|
@ -1421,15 +1422,13 @@ export function Table({
|
|||
{page.map((row, index) => {
|
||||
prepareRow(row);
|
||||
let rowProps = { ...row.getRowProps() };
|
||||
const contentWrap = resolveReferences(contentWrapProperty, currentState);
|
||||
const contentWrap = resolveReferences(contentWrapProperty);
|
||||
const isMaxRowHeightAuto = maxRowHeight === 'auto';
|
||||
rowProps.style.minHeight = cellSize === 'condensed' ? '39px' : '45px'; // 1px is removed to accomodate 1px border-bottom
|
||||
let cellMaxHeight;
|
||||
let cellHeight;
|
||||
if (contentWrap) {
|
||||
cellMaxHeight = isMaxRowHeightAuto
|
||||
? 'fit-content'
|
||||
: resolveReferences(maxRowHeightValue, currentState) + 'px';
|
||||
cellMaxHeight = isMaxRowHeightAuto ? 'fit-content' : resolveReferences(maxRowHeightValue) + 'px';
|
||||
rowProps.style.maxHeight = cellMaxHeight;
|
||||
} else {
|
||||
cellMaxHeight = cellSize === 'condensed' ? 40 : 46;
|
||||
|
|
@ -1523,25 +1522,25 @@ export function Table({
|
|||
'multiselect',
|
||||
'toggle',
|
||||
].includes(cell?.column?.columnType)
|
||||
? resolveReferences(cell.column?.cellBackgroundColor, currentState, '', {
|
||||
? resolveReferences(cell.column?.cellBackgroundColor, '', {
|
||||
cellValue,
|
||||
rowData,
|
||||
})
|
||||
: '';
|
||||
const cellTextColor = resolveReferences(cell.column?.textColor, currentState, '', {
|
||||
const cellTextColor = resolveReferences(cell.column?.textColor, '', {
|
||||
cellValue,
|
||||
rowData,
|
||||
});
|
||||
const actionButtonsArray = actions.map((action) => {
|
||||
return {
|
||||
...action,
|
||||
isDisabled: resolveReferences(action?.disableActionButton ?? false, currentState, '', {
|
||||
isDisabled: resolveReferences(action?.disableActionButton ?? false, '', {
|
||||
cellValue,
|
||||
rowData,
|
||||
}),
|
||||
};
|
||||
});
|
||||
const isEditable = resolveReferences(cell.column?.isEditable ?? false, currentState, '', {
|
||||
const isEditable = resolveReferences(cell.column?.isEditable ?? false, '', {
|
||||
cellValue,
|
||||
rowData,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ export default function generateColumnsData({
|
|||
columnType === 'image'
|
||||
) {
|
||||
columnOptions.selectOptions = [];
|
||||
const values = resolveReferences(column.values, currentState, []);
|
||||
const labels = resolveReferences(column.labels, currentState, []);
|
||||
const values = resolveReferences(column.values, []);
|
||||
const labels = resolveReferences(column.labels, []);
|
||||
|
||||
if (Array.isArray(labels) && Array.isArray(values)) {
|
||||
columnOptions.selectOptions = labels.map((label, index) => {
|
||||
|
|
@ -62,9 +62,9 @@ export default function generateColumnsData({
|
|||
}
|
||||
if (columnType === 'select' || columnType === 'newMultiSelect') {
|
||||
columnOptions.selectOptions = [];
|
||||
const useDynamicOptions = resolveReferences(column?.useDynamicOptions, currentState);
|
||||
const useDynamicOptions = resolveReferences(column?.useDynamicOptions);
|
||||
if (useDynamicOptions) {
|
||||
const dynamicOptions = resolveReferences(column?.dynamicOptions || [], currentState);
|
||||
const dynamicOptions = resolveReferences(column?.dynamicOptions || []);
|
||||
columnOptions.selectOptions = Array.isArray(dynamicOptions) ? dynamicOptions : [];
|
||||
} else {
|
||||
const options = column?.options ?? [];
|
||||
|
|
@ -103,7 +103,7 @@ export default function generateColumnsData({
|
|||
const width = columnSize || defaultColumn.width;
|
||||
return {
|
||||
id: column.id,
|
||||
Header: resolveReferences(column.name, currentState) ?? '',
|
||||
Header: resolveReferences(column.name) ?? '',
|
||||
accessor: column.key || column.name,
|
||||
filter: customFilter,
|
||||
width: width,
|
||||
|
|
@ -150,7 +150,7 @@ export default function generateColumnsData({
|
|||
case 'string':
|
||||
case undefined:
|
||||
case 'default': {
|
||||
const cellTextColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
|
||||
const cellTextColor = resolveReferences(column.textColor, '', { cellValue, rowData });
|
||||
return (
|
||||
<StringColumn
|
||||
isEditable={isEditable}
|
||||
|
|
@ -252,7 +252,7 @@ export default function generateColumnsData({
|
|||
// );
|
||||
}
|
||||
case 'number': {
|
||||
const textColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
|
||||
const textColor = resolveReferences(column.textColor, '', { cellValue, rowData });
|
||||
|
||||
const cellStyles = {
|
||||
color: textColor ?? '',
|
||||
|
|
@ -303,7 +303,7 @@ export default function generateColumnsData({
|
|||
|
||||
const allowedDecimalPlaces = column?.decimalPlaces ?? null;
|
||||
const removingExcessDecimalPlaces = (cellValue, allowedDecimalPlaces) => {
|
||||
allowedDecimalPlaces = resolveReferences(allowedDecimalPlaces, currentState);
|
||||
allowedDecimalPlaces = resolveReferences(allowedDecimalPlaces);
|
||||
if (cellValue?.toString()?.includes('.')) {
|
||||
const splittedCellValue = cellValue?.toString()?.split('.');
|
||||
const decimalPlacesUnderLimit = splittedCellValue[1]
|
||||
|
|
@ -486,8 +486,7 @@ export default function generateColumnsData({
|
|||
isMulti={columnType === 'newMultiSelect' ? true : false}
|
||||
containerWidth={width}
|
||||
optionsLoadingState={
|
||||
resolveReferences(column?.useDynamicOptions, currentState) &&
|
||||
resolveReferences(column?.optionsLoadingState, currentState)
|
||||
resolveReferences(column?.useDynamicOptions) && resolveReferences(column?.optionsLoadingState)
|
||||
? true
|
||||
: false
|
||||
}
|
||||
|
|
@ -610,12 +609,12 @@ export default function generateColumnsData({
|
|||
);
|
||||
}
|
||||
case 'datepicker': {
|
||||
const textColor = resolveReferences(column.textColor, currentState, '', { cellValue, rowData });
|
||||
const isTimeChecked = resolveReferences(column?.isTimeChecked, currentState);
|
||||
const isTwentyFourHrFormatEnabled = resolveReferences(column?.isTwentyFourHrFormatEnabled, currentState);
|
||||
const disabledDates = resolveReferences(column?.disabledDates, currentState);
|
||||
const parseInUnixTimestamp = resolveReferences(column?.parseInUnixTimestamp, currentState);
|
||||
const isDateSelectionEnabled = resolveReferences(column?.isDateSelectionEnabled, currentState);
|
||||
const textColor = resolveReferences(column.textColor, '', { cellValue, rowData });
|
||||
const isTimeChecked = resolveReferences(column?.isTimeChecked);
|
||||
const isTwentyFourHrFormatEnabled = resolveReferences(column?.isTwentyFourHrFormatEnabled);
|
||||
const disabledDates = resolveReferences(column?.disabledDates);
|
||||
const parseInUnixTimestamp = resolveReferences(column?.parseInUnixTimestamp);
|
||||
const isDateSelectionEnabled = resolveReferences(column?.isDateSelectionEnabled);
|
||||
const cellStyles = {
|
||||
color: textColor ?? '',
|
||||
};
|
||||
|
|
@ -684,7 +683,7 @@ export default function generateColumnsData({
|
|||
);
|
||||
}
|
||||
case 'link': {
|
||||
const linkTarget = resolveReferences(column?.linkTarget ?? '{{true}}', currentState);
|
||||
const linkTarget = resolveReferences(column?.linkTarget ?? '{{true}}');
|
||||
column = {
|
||||
...column,
|
||||
linkColor: column?.linkColor ?? '#1B1F24',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable import/no-named-as-default */
|
||||
import React, { useCallback, useState, useEffect, useRef, useMemo, useContext } from 'react';
|
||||
import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { useDrop, useDragLayer } from 'react-dnd';
|
||||
import { ItemTypes, EditorConstants } from './editorConstants';
|
||||
|
|
@ -17,7 +17,7 @@ import { useAppVersionStore } from '@/_stores/appVersionStore';
|
|||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
import { useAppInfo } from '@/_stores/appDataStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import _, { cloneDeep, isEmpty } from 'lodash';
|
||||
import _, { isEmpty } from 'lodash';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
import DragContainer from './DragContainer';
|
||||
|
|
@ -32,6 +32,7 @@ import './editor.theme.scss';
|
|||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import BulkIcon from '@/_ui/Icon/BulkIcons';
|
||||
import { getSubpath } from '@/_helpers/routes';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
const deviceWindowWidth = EditorConstants.deviceWindowWidth;
|
||||
|
||||
|
|
@ -123,9 +124,9 @@ export const Container = ({
|
|||
const mobLayouts = Object.keys(boxes)
|
||||
.filter((key) => !boxes[key]?.component?.parent)
|
||||
.map((key) => {
|
||||
return { ...cloneDeep(boxes[key]?.layouts?.desktop), i: key };
|
||||
return { ...deepClone(boxes[key]?.layouts?.desktop), i: key };
|
||||
});
|
||||
const updatedBoxes = cloneDeep(boxes);
|
||||
const updatedBoxes = deepClone(boxes);
|
||||
let newmMobLayouts = correctBounds(mobLayouts, { cols: 43 });
|
||||
newmMobLayouts = compact(newmMobLayouts, 'vertical', 43);
|
||||
Object.keys(boxes).forEach((id) => {
|
||||
|
|
@ -182,9 +183,9 @@ export const Container = ({
|
|||
const mobLayouts = Object.keys(components)
|
||||
.filter((key) => !components[key]?.component?.parent)
|
||||
.map((key) => {
|
||||
return { ...cloneDeep(components[key]?.layouts?.desktop), i: key };
|
||||
return { ...deepClone(components[key]?.layouts?.desktop), i: key };
|
||||
});
|
||||
const updatedBoxes = cloneDeep(components);
|
||||
const updatedBoxes = deepClone(components);
|
||||
let newmMobLayouts = correctBounds(mobLayouts, { cols: 43 });
|
||||
newmMobLayouts = compact(newmMobLayouts, 'vertical', 43);
|
||||
Object.keys(components).forEach((id) => {
|
||||
|
|
@ -257,16 +258,18 @@ export const Container = ({
|
|||
return;
|
||||
}
|
||||
|
||||
if (!appDefinition.pages[currentPageId]?.components) return;
|
||||
const definition = useEditorStore.getState().appDefinition;
|
||||
|
||||
if (!definition.pages[currentPageId]?.components) return;
|
||||
|
||||
const newDefinition = {
|
||||
...appDefinition,
|
||||
...definition,
|
||||
pages: {
|
||||
...appDefinition.pages,
|
||||
...definition.pages,
|
||||
[currentPageId]: {
|
||||
...appDefinition.pages[currentPageId],
|
||||
...definition.pages[currentPageId],
|
||||
components: {
|
||||
...appDefinition.pages[currentPageId]?.components,
|
||||
...definition.pages[currentPageId]?.components,
|
||||
...boxes,
|
||||
},
|
||||
},
|
||||
|
|
@ -275,7 +278,7 @@ export const Container = ({
|
|||
|
||||
//need to check if a new component is added or deleted
|
||||
|
||||
const oldComponents = appDefinition.pages[currentPageId]?.components ?? {};
|
||||
const oldComponents = definition.pages[currentPageId]?.components ?? {};
|
||||
const newComponents = boxes;
|
||||
|
||||
const componendAdded = Object.keys(newComponents).length > Object.keys(oldComponents).length;
|
||||
|
|
@ -288,7 +291,8 @@ export const Container = ({
|
|||
opts.componentAdded = true;
|
||||
}
|
||||
|
||||
const shouldUpdate = !_.isEmpty(diff(appDefinition, newDefinition));
|
||||
const shouldUpdate = !_.isEmpty(diff(definition, newDefinition));
|
||||
|
||||
if (shouldUpdate) {
|
||||
appDefinitionChanged(newDefinition, opts);
|
||||
}
|
||||
|
|
@ -366,7 +370,7 @@ export const Container = ({
|
|||
}
|
||||
|
||||
const canvasBoundingRect = document.getElementsByClassName('real-canvas')[0].getBoundingClientRect();
|
||||
const componentMeta = _.cloneDeep(
|
||||
const componentMeta = deepClone(
|
||||
componentTypes.find((component) => component.component === item.component.component)
|
||||
);
|
||||
|
||||
|
|
@ -390,15 +394,13 @@ export const Container = ({
|
|||
Listview: 'listItem',
|
||||
});
|
||||
const customResolverVariable = widgetResolvables[parentMeta?.component];
|
||||
const defaultChildren = _.cloneDeep(parentMeta)['defaultChildren'];
|
||||
const defaultChildren = deepClone(parentMeta)['defaultChildren'];
|
||||
const parentId = newComponent.id;
|
||||
|
||||
defaultChildren.forEach((child) => {
|
||||
const { componentName, layout, incrementWidth, properties, accessorKey, tab, defaultValue, styles } = child;
|
||||
|
||||
const componentMeta = _.cloneDeep(
|
||||
componentTypes.find((component) => component.component === componentName)
|
||||
);
|
||||
const componentMeta = deepClone(componentTypes.find((component) => component.component === componentName));
|
||||
const componentData = JSON.parse(JSON.stringify(componentMeta));
|
||||
|
||||
const width = layout.width ? layout.width : (componentMeta.defaultSize.width * 100) / noOfGrids;
|
||||
|
|
@ -818,6 +820,8 @@ export const Container = ({
|
|||
? 'Connect to your data source or use our sample data source to start playing around!'
|
||||
: 'Connect to a data source to be able to create a query';
|
||||
|
||||
const showEmptyContainer = !appLoading && !isDragging && mode !== 'view';
|
||||
|
||||
return (
|
||||
<ContainerWrapper
|
||||
showComments={showComments}
|
||||
|
|
@ -930,7 +934,7 @@ export const Container = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
{Object.keys(boxes).length === 0 && !appLoading && !isDragging && (
|
||||
{Object.keys(boxes).length === 0 && showEmptyContainer && (
|
||||
<div style={{ paddingTop: '10%' }}>
|
||||
<div className="row empty-box-cont">
|
||||
<div className="col-md-4 dotted-cont">
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const shouldUpdate = (prevProps, nextProps) => {
|
|||
return (
|
||||
deepEqualityCheckusingLoDash(prevProps?.id, nextProps?.id) &&
|
||||
deepEqualityCheckusingLoDash(prevProps?.component?.definition, nextProps?.component?.definition) &&
|
||||
deepEqualityCheckusingLoDash(prevProps?.customResolvables, nextProps?.customResolvables) &&
|
||||
prevProps?.width === nextProps?.width &&
|
||||
prevProps?.height === nextProps?.height &&
|
||||
prevProps?.darkMode === nextProps?.darkMode &&
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import { useNoOfGrid, useGridStore } from '@/_stores/gridStore';
|
|||
import WidgetBox from './WidgetBox';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { findHighestLevelofSelection } from './DragContainer';
|
||||
import { useCurrentStateStore } from '@/_stores/currentStateStore';
|
||||
import { useResolveStore } from '@/_stores/resolverStore';
|
||||
|
||||
function computeWidth(currentLayoutOptions) {
|
||||
return `${currentLayoutOptions?.width}%`;
|
||||
|
|
@ -143,6 +145,16 @@ const DraggableBox = React.memo(
|
|||
[id]
|
||||
);
|
||||
|
||||
const isEditorReady = useCurrentStateStore.getState().isEditorReady;
|
||||
const isResolverStoreReady = useResolveStore.getState().storeReady;
|
||||
|
||||
const isCanvasReady = isEditorReady && isResolverStoreReady;
|
||||
/**
|
||||
* !This component does not consume the value returned from the below hook.
|
||||
* Only purpose of the hook is to force one rerender the component
|
||||
* */
|
||||
useEditorStore((state) => state.componentsNeedsUpdateOnNextRender.find((compId) => compId === id));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
|
|
@ -228,6 +240,7 @@ const DraggableBox = React.memo(
|
|||
onOptionsChanged={onComponentOptionsChanged}
|
||||
isFromSubContainer={isFromSubContainer}
|
||||
childComponents={childComponents}
|
||||
isCanvasReady={isCanvasReady}
|
||||
/>
|
||||
</Sentry.ErrorBoundary>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -46,13 +46,8 @@ import { withTranslation } from 'react-i18next';
|
|||
import { v4 as uuid } from 'uuid';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import EditorHeader from './Header';
|
||||
import {
|
||||
getWorkspaceId,
|
||||
isValidUUID,
|
||||
setWindowTitle,
|
||||
defaultWhiteLabellingSettings,
|
||||
pageTitles,
|
||||
} from '@/_helpers/utils';
|
||||
import { getWorkspaceId, isValidUUID } from '@/_helpers/utils';
|
||||
import { fetchAndSetWindowTitle, pageTitles, defaultWhiteLabellingSettings } from '@white-label/whiteLabelling';
|
||||
import '@/_styles/editor/react-select-search.scss';
|
||||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import { ReleasedVersionError } from './AppVersionsManager/ReleasedVersionError';
|
||||
|
|
@ -69,7 +64,7 @@ import {
|
|||
resetAllStores,
|
||||
} from '@/_stores/utils';
|
||||
import { setCookie } from '@/_helpers/cookie';
|
||||
import { EMPTY_ARRAY, flushComponentsToRender, useEditorActions, useEditorStore } from '@/_stores/editorStore';
|
||||
import { EMPTY_ARRAY, useEditorActions, useEditorStore } from '@/_stores/editorStore';
|
||||
import { useAppDataActions, useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { useNoOfGrid } from '@/_stores/gridStore';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
|
|
@ -87,14 +82,10 @@ import { HotkeysProvider } from 'react-hotkeys-hook';
|
|||
import { useResolveStore } from '@/_stores/resolverStore';
|
||||
import { dfs } from '@/_stores/handleReferenceTransactions';
|
||||
import { decimalToHex, EditorConstants } from './editorConstants';
|
||||
import {
|
||||
findComponentsWithReferences,
|
||||
handleLowPriorityWork,
|
||||
updateCanvasBackground,
|
||||
clearAllQueuedTasks,
|
||||
} from '@/_helpers/editorHelpers';
|
||||
import { handleLowPriorityWork, updateCanvasBackground, clearAllQueuedTasks } from '@/_helpers/editorHelpers';
|
||||
import { TJLoader } from '@/_ui/TJLoader/TJLoader';
|
||||
import cx from 'classnames';
|
||||
import { resolveReferences } from './CodeEditor/utils';
|
||||
|
||||
setAutoFreeze(false);
|
||||
enablePatches();
|
||||
|
|
@ -301,65 +292,14 @@ const EditorComponent = (props) => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify({ appDefinition, currentPageId, dataQueries })]);
|
||||
|
||||
/**
|
||||
** Async updates components in batches to optimize and processing efficiency.
|
||||
* This function iterates over an array of component IDs, updating them in fixed-size batches,
|
||||
* and introduces a delay after each batch to allow the UI thread to manage other tasks, such as rendering updates.
|
||||
* After all batches are processed, it flushes the updates to clear any flags or temporary states indicating pending updates,
|
||||
* ensuring the system is ready for the next cycle of updates.
|
||||
*
|
||||
* @param {Array} componentIds An array of component IDs that need updates.
|
||||
* @returns {Promise<void>} A promise that resolves once all batches have been processed and flushed.
|
||||
*/
|
||||
|
||||
async function batchUpdateComponents(componentIds) {
|
||||
if (componentIds.length === 0) return;
|
||||
|
||||
let updatedComponentIds = [];
|
||||
|
||||
for (let i = 0; i < componentIds.length; i += 10) {
|
||||
const batch = componentIds.slice(i, i + 10);
|
||||
batch.forEach((id) => {
|
||||
updatedComponentIds.push(id);
|
||||
});
|
||||
|
||||
updateComponentsNeedsUpdateOnNextRender(batch);
|
||||
// Delay to allow UI to process
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
// Flush only updated components
|
||||
|
||||
flushComponentsToRender(updatedComponentIds);
|
||||
}
|
||||
|
||||
const lastUpdatedRef = useResolveStore((state) => state.lastUpdatedRefs, shallow);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastUpdatedRef.length > 0) {
|
||||
const currentComponents = useEditorStore.getState().appDefinition?.pages?.[currentPageId]?.components || {};
|
||||
|
||||
const directRenders = lastUpdatedRef.map((ref) => ref.includes('rerender') && ref.split(' ')[1]);
|
||||
|
||||
const toUpdateRefs = lastUpdatedRef.filter((ref) => !ref.includes('rerender'));
|
||||
|
||||
const componentIdsWithReferences = findComponentsWithReferences(currentComponents, toUpdateRefs);
|
||||
|
||||
if (directRenders.length > 0) {
|
||||
componentIdsWithReferences.push(...directRenders);
|
||||
}
|
||||
|
||||
if (componentIdsWithReferences.length > 0) {
|
||||
batchUpdateComponents(componentIdsWithReferences);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [lastUpdatedRef]);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const components = appDefinition?.pages?.[currentPageId]?.components || {};
|
||||
computeComponentState(components);
|
||||
const isEditorReady = useCurrentStateStore.getState().isEditorReady;
|
||||
const isResolverStoreReady = useResolveStore.getState().storeReady;
|
||||
if (isEditorReady && isResolverStoreReady) {
|
||||
const components = appDefinition?.pages?.[currentPageId]?.components || {};
|
||||
computeComponentState(components);
|
||||
}
|
||||
|
||||
const isPageSwitched = useResolveStore.getState().isPageSwitched;
|
||||
|
||||
|
|
@ -406,6 +346,11 @@ const EditorComponent = (props) => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentLayout, mounted]);
|
||||
|
||||
useEffect(() => {
|
||||
updateEntityReferences(appDefinition, currentPageId);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [events.length]);
|
||||
|
||||
const handleYmapEventUpdates = () => {
|
||||
props.ymap?.set('eventHandlersUpdated', {
|
||||
currentVersionId: currentVersionId,
|
||||
|
|
@ -621,7 +566,7 @@ const EditorComponent = (props) => {
|
|||
app.name = newName;
|
||||
updateState({ appName: newName, app: app });
|
||||
updateState({ appName: newName });
|
||||
setWindowTitle({ page: pageTitles.EDITOR, appName: newName });
|
||||
fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName: newName });
|
||||
};
|
||||
|
||||
const onZoomChanged = (zoom) => {
|
||||
|
|
@ -760,7 +705,7 @@ const EditorComponent = (props) => {
|
|||
} = appData;
|
||||
|
||||
const startingPageHandle = props.params.pageHandle;
|
||||
setWindowTitle({ page: pageTitles.EDITOR, appName });
|
||||
fetchAndSetWindowTitle({ page: pageTitles.EDITOR, appName });
|
||||
useAppVersionStore.getState().actions.updateEditingVersion(editing_version);
|
||||
current_version_id && useAppVersionStore.getState().actions.updateReleasedVersionId(current_version_id);
|
||||
await fetchOrgEnvironmentConstants();
|
||||
|
|
@ -777,21 +722,23 @@ const EditorComponent = (props) => {
|
|||
app: appData,
|
||||
});
|
||||
|
||||
await useDataSourcesStore.getState().actions.fetchGlobalDataSources(organizationId);
|
||||
await fetchDataSources(editing_version?.id);
|
||||
|
||||
await processNewAppDefinition(appData, startingPageHandle, false, ({ homePageId }) => {
|
||||
handleLowPriorityWork(async () => {
|
||||
handleLowPriorityWork(() => {
|
||||
useResolveStore.getState().actions.updateLastUpdatedRefs(['constants', 'client']);
|
||||
await useDataSourcesStore.getState().actions.fetchGlobalDataSources(organizationId);
|
||||
await fetchDataSources(editing_version?.id);
|
||||
commonLowPriorityActions(events, { homePageId });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const commonLowPriorityActions = async (events, { homePageId }) => {
|
||||
const commonLowPriorityActions = (events, { homePageId }) => {
|
||||
const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === homePageId);
|
||||
const editorRef = getEditorRef();
|
||||
await runQueries(useDataQueriesStore.getState().dataQueries, editorRef, true);
|
||||
await handleEvent('onPageLoad', currentPageEvents, {}, true);
|
||||
runQueries(useDataQueriesStore.getState().dataQueries, editorRef, true).then(() => {
|
||||
handleEvent('onPageLoad', currentPageEvents, {}, true);
|
||||
});
|
||||
};
|
||||
|
||||
const processNewAppDefinition = async (data, startingPageHandle, versionSwitched = false, onComplete) => {
|
||||
|
|
@ -1132,12 +1079,13 @@ const EditorComponent = (props) => {
|
|||
isUpdatingEditorStateInProcess: false,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((err) => {
|
||||
updateEditorState({
|
||||
saveError: true,
|
||||
isUpdatingEditorStateInProcess: false,
|
||||
});
|
||||
toast.error('App could not save.');
|
||||
// toast.error('App could not save.');
|
||||
toast.error(err?.error ?? 'App could not save.');
|
||||
})
|
||||
.finally(() => {
|
||||
if (appDiffOptions?.cloningComponent) {
|
||||
|
|
@ -1509,6 +1457,7 @@ const EditorComponent = (props) => {
|
|||
newGlobalSettings = dfs(newGlobalSettings, entity, value);
|
||||
}
|
||||
});
|
||||
const [_, error, resolvedCanvasBackgroundColor] = resolveReferences(newGlobalSettings?.backgroundFxQuery, {});
|
||||
|
||||
const newAppDefinition = produce(appJson, (draft) => {
|
||||
draft.globalSettings = newGlobalSettings;
|
||||
|
|
@ -1517,7 +1466,7 @@ const EditorComponent = (props) => {
|
|||
// Setting the canvas background to the editor store
|
||||
setCanvasBackground({
|
||||
backgroundFxQuery: newGlobalSettings?.backgroundFxQuery,
|
||||
canvasBackgroundColor: newGlobalSettings?.canvasBackgroundColor,
|
||||
canvasBackgroundColor: resolvedCanvasBackgroundColor || '',
|
||||
});
|
||||
|
||||
updateEditorState({
|
||||
|
|
@ -1818,7 +1767,9 @@ const EditorComponent = (props) => {
|
|||
});
|
||||
|
||||
const copyOfAppDefinition = JSON.parse(JSON.stringify(appDefinition));
|
||||
const newCurrentPageId = isHomePage ? Object.keys(copyOfAppDefinition.pages)[0] : copyOfAppDefinition.homePageId;
|
||||
|
||||
setCurrentPageId(newCurrentPageId);
|
||||
const toBeDeletedPage = copyOfAppDefinition.pages[pageId];
|
||||
|
||||
const newAppDefinition = {
|
||||
|
|
@ -1826,9 +1777,6 @@ const EditorComponent = (props) => {
|
|||
pages: omit(copyOfAppDefinition.pages, pageId),
|
||||
};
|
||||
|
||||
const newCurrentPageId = isHomePage ? Object.keys(copyOfAppDefinition.pages)[0] : copyOfAppDefinition.homePageId;
|
||||
|
||||
setCurrentPageId(newCurrentPageId);
|
||||
updateEditorState({
|
||||
isUpdatingEditorStateInProcess: true,
|
||||
});
|
||||
|
|
@ -1840,8 +1788,6 @@ const EditorComponent = (props) => {
|
|||
});
|
||||
|
||||
toast.success(`${toBeDeletedPage.name} page deleted.`);
|
||||
|
||||
switchPage(newCurrentPageId);
|
||||
};
|
||||
|
||||
const disableEnablePage = ({ pageId, isDisabled }) => {
|
||||
|
|
@ -1907,7 +1853,7 @@ const EditorComponent = (props) => {
|
|||
setIsSaving(true);
|
||||
appVersionService
|
||||
.clonePage(appId, editingVersionId, pageId)
|
||||
.then((data) => {
|
||||
.then(async (data) => {
|
||||
const copyOfAppDefinition = JSON.parse(JSON.stringify(appDefinition));
|
||||
|
||||
const pages = data.pages.reduce((acc, page) => {
|
||||
|
|
@ -1931,6 +1877,8 @@ const EditorComponent = (props) => {
|
|||
events: data.events,
|
||||
});
|
||||
appDefinitionChanged(newAppDefinition);
|
||||
await onEditorLoad(newAppDefinition, pageId, false);
|
||||
updateEntityReferences(newAppDefinition, pageId);
|
||||
})
|
||||
.finally(() => setIsSaving(false));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ export const GlobalSettings = ({
|
|||
lineNumbers={false}
|
||||
onChange={(color) => {
|
||||
const options = {
|
||||
canvasBackgroundColor: resolveReferences(color, realState),
|
||||
canvasBackgroundColor: resolveReferences(color),
|
||||
backgroundFxQuery: color,
|
||||
};
|
||||
globalSettingsChanged(options);
|
||||
|
|
|
|||
|
|
@ -14,11 +14,13 @@ import cx from 'classnames';
|
|||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import { TOOLTIP_MESSAGES } from '@/_helpers/constants';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
class ManageAppUsersComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.isUserAdmin = authenticationService.currentSessionValue?.admin;
|
||||
this.whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
this.state = {
|
||||
showModal: false,
|
||||
|
|
@ -175,7 +177,7 @@ class ManageAppUsersComponent extends React.Component {
|
|||
const appLink = `${getHostURL()}/applications/`;
|
||||
const shareableLink = appLink + (this.props.slug || appId);
|
||||
const slugButtonClass = !_.isEmpty(newSlug.error) ? 'is-invalid' : 'is-valid';
|
||||
const embeddableLink = `<iframe width="560" height="315" src="${appLink}${this.props.slug}" title="Tooljet app - ${this.props.slug}" frameborder="0" allowfullscreen></iframe>`;
|
||||
const embeddableLink = `<iframe width="560" height="315" src="${appLink}${this.props.slug}" title="${this.whiteLabelText} app - ${this.props.slug}" frameborder="0" allowfullscreen></iframe>`;
|
||||
const shouldWeDisableShareModal = !this.props.isVersionReleased;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default function EditorHeader({
|
|||
: '';
|
||||
setAppPreviewLink(appVersionPreviewLink);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [slug, currentVersionId, editingVersion]);
|
||||
}, [slug, currentVersionId, editingVersion, pageHandle]);
|
||||
|
||||
return (
|
||||
<div className={cx('header', { 'dark-theme theme-dark': darkMode })} style={{ width: '100%' }}>
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ export const baseComponentProperties = (
|
|||
Layout: [],
|
||||
};
|
||||
if (component.component.component === 'Listview') {
|
||||
if (!resolveReferences(component.component.definition.properties?.enablePagination?.value, currentState)) {
|
||||
if (!resolveReferences(component.component.definition.properties?.enablePagination?.value)) {
|
||||
properties = properties.filter((property) => property !== 'rowsPerPage');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,7 @@ export const FilePicker = ({ componentMeta, darkMode, ...restProps }) => {
|
|||
return renderElement(component, componentMeta, paramUpdated, dataQueries, param, paramType, currentState);
|
||||
};
|
||||
const conditionalAccordionItems = (component) => {
|
||||
const parseContent = resolveReferences(
|
||||
component.component.definition.properties.parseContent?.value ?? false,
|
||||
currentState
|
||||
);
|
||||
const parseContent = resolveReferences(component.component.definition.properties.parseContent?.value ?? false);
|
||||
const accordionItems = [];
|
||||
const options = ['parseContent'];
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ export const Modal = ({ componentMeta, darkMode, ...restProps }) => {
|
|||
};
|
||||
const conditionalAccordionItems = (component) => {
|
||||
const useDefaultButton = resolveReferences(
|
||||
component.component.definition.properties.useDefaultButton?.value ?? false,
|
||||
currentState
|
||||
component.component.definition.properties.useDefaultButton?.value ?? false
|
||||
);
|
||||
const accordionItems = [];
|
||||
const options = ['useDefaultButton'];
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ const DatepickerProperties = ({ column, index, darkMode, currentState, onColumnI
|
|||
paramMeta={{ type: 'toggle', displayName: 'Enable date' }}
|
||||
/>
|
||||
</div>
|
||||
{resolveReferences(column?.isDateSelectionEnabled, currentState) && (
|
||||
{resolveReferences(column?.isDateSelectionEnabled) && (
|
||||
<div
|
||||
data-cy={`input-date-display-format`}
|
||||
className="field mb-2 w-100"
|
||||
|
|
@ -175,7 +175,7 @@ const DatepickerProperties = ({ column, index, darkMode, currentState, onColumnI
|
|||
paramMeta={{ type: 'toggle', displayName: 'Enable time' }}
|
||||
/>
|
||||
</div>
|
||||
{resolveReferences(column?.isTimeChecked, currentState) && (
|
||||
{resolveReferences(column?.isTimeChecked) && (
|
||||
<>
|
||||
{!isDateDisplayFormatFxOn && (
|
||||
<div className="field mb-2" onClick={(e) => e.stopPropagation()} style={{ padding: '0px 12px' }}>
|
||||
|
|
@ -260,7 +260,7 @@ const DatepickerProperties = ({ column, index, darkMode, currentState, onColumnI
|
|||
paramType="properties"
|
||||
paramMeta={{ type: 'toggle', displayName: 'Parse in unix timestamp' }}
|
||||
/>
|
||||
{resolveReferences(column?.parseInUnixTimestamp, currentState) ? (
|
||||
{resolveReferences(column?.parseInUnixTimestamp) ? (
|
||||
<div className="mt-2">
|
||||
<div className="field mb-2 tj-app-input">
|
||||
<label data-cy={`label-date-parse-format`} className="form-label">
|
||||
|
|
@ -283,7 +283,7 @@ const DatepickerProperties = ({ column, index, darkMode, currentState, onColumnI
|
|||
</div>
|
||||
) : (
|
||||
<div className="mt-2">
|
||||
{resolveReferences(column?.isDateSelectionEnabled, currentState) && (
|
||||
{resolveReferences(column?.isDateSelectionEnabled) && (
|
||||
<div data-cy={`input-parse-timezone`} className="field mb-2">
|
||||
<div className="d-flex justify-content-between">
|
||||
<label data-cy={`label-parse-timezone`} className="form-label">
|
||||
|
|
@ -331,7 +331,7 @@ const DatepickerProperties = ({ column, index, darkMode, currentState, onColumnI
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{resolveReferences(column?.isTimeChecked, currentState) && (
|
||||
{resolveReferences(column?.isTimeChecked) && (
|
||||
<>
|
||||
{!isParseDateFormatFxOn && (
|
||||
<div className="field mb-2" onClick={(e) => e.stopPropagation()}>
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ export const PropertiesTabElements = ({
|
|||
paramType="properties"
|
||||
/>
|
||||
</div>
|
||||
{resolveReferences(column?.isEditable, currentState) && (
|
||||
{resolveReferences(column?.isEditable) && (
|
||||
<ValidationProperties
|
||||
column={column}
|
||||
index={index}
|
||||
|
|
|
|||
|
|
@ -111,9 +111,7 @@ export const ProgramaticallyHandleProperties = ({
|
|||
const fxActiveFieldsPropExists = props?.hasOwnProperty('fxActiveFields') ?? false;
|
||||
//to support backward compatibility, when fxActive is true for a particular column, we are passing all possible combinations which should render codehinter
|
||||
const fxActive =
|
||||
props?.fxActive && resolveReferences(props.fxActive, currentState)
|
||||
? ['isEditable', 'columnVisibility', 'linkTarget']
|
||||
: [];
|
||||
props?.fxActive && resolveReferences(props.fxActive) ? ['isEditable', 'columnVisibility', 'linkTarget'] : [];
|
||||
|
||||
const checkFxActiveFieldIsArrray = (fxActiveFieldsProperty) => {
|
||||
// adding error handling mechanism for fxActiveFieldsProperty , if props.fxActiveFields is array , then return props.fxActiveFields or else return [], this will make sure, fxActiveFields wil always be array
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ export const OptionsList = ({
|
|||
const options = column.options;
|
||||
options[optionIndex][property] = value;
|
||||
column.options = options;
|
||||
const isValueTruthy = !!resolveReferences(value, currentState);
|
||||
const isValueTruthy = !!resolveReferences(value);
|
||||
|
||||
// This block is responsible for updating list of defaultOptions when makeDefaultOption prop is updated
|
||||
if (property === 'makeDefaultOption') {
|
||||
|
|
@ -235,7 +235,7 @@ export const OptionsList = ({
|
|||
}}
|
||||
paramType="properties"
|
||||
/>
|
||||
{resolveReferences(column?.useDynamicOptions, currentState) ? (
|
||||
{resolveReferences(column?.useDynamicOptions) ? (
|
||||
<div className="d-flex custom-gap-7 flex-column">
|
||||
<CodeHinter
|
||||
currentState={currentState}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import NoListItem from './NoListItem';
|
|||
import { ProgramaticallyHandleProperties } from './ProgramaticallyHandleProperties';
|
||||
import { ColumnPopoverContent } from './ColumnManager/ColumnPopover';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import CodeHinter from '@/Editor/CodeEditor';
|
||||
|
||||
import { checkIfTableColumnDeprecated } from './ColumnManager/DeprecatedColumnTypeMsg';
|
||||
|
||||
|
|
@ -82,7 +81,7 @@ class TableComponent extends React.Component {
|
|||
checkIfAllColumnsAreEditable = (component) => {
|
||||
const isAllColumnsEditable = component.component?.definition?.properties?.columns?.value
|
||||
?.filter((column) => !NON_EDITABLE_COLUMNS.includes(column.columnType))
|
||||
.every((column) => resolveReferences(column.isEditable, this.props.currentState));
|
||||
.every((column) => resolveReferences(column.isEditable));
|
||||
return isAllColumnsEditable;
|
||||
};
|
||||
|
||||
|
|
@ -92,7 +91,7 @@ class TableComponent extends React.Component {
|
|||
if (prevPropsColumns !== currentPropsColumns) {
|
||||
const isAllColumnsEditable = currentPropsColumns
|
||||
.filter((column) => !NON_EDITABLE_COLUMNS.includes(column.columnType))
|
||||
.every((column) => resolveReferences(column.isEditable, this.props.currentState));
|
||||
.every((column) => resolveReferences(column.isEditable));
|
||||
this.setState({ isAllColumnsEditable });
|
||||
}
|
||||
}
|
||||
|
|
@ -172,7 +171,7 @@ class TableComponent extends React.Component {
|
|||
className={`${this.props.darkMode && 'dark-theme'} shadow table-column-popover`}
|
||||
style={{
|
||||
width: '280px',
|
||||
maxHeight: resolveReferences(column.isEditable, this.state.currentState) ? '100vh' : 'inherit',
|
||||
maxHeight: resolveReferences(column.isEditable) ? '100vh' : 'inherit',
|
||||
overflowY: 'auto',
|
||||
zIndex: '9999',
|
||||
}}
|
||||
|
|
@ -470,10 +469,7 @@ class TableComponent extends React.Component {
|
|||
`component/${this.props.component.component.name}/${column ?? 'default'}::${field}`;
|
||||
|
||||
handleMakeAllColumnsEditable = (value) => {
|
||||
const columns = resolveReferences(
|
||||
this.props.component.component.definition.properties.columns,
|
||||
this.props.currentState
|
||||
);
|
||||
const columns = resolveReferences(this.props.component.component.definition.properties.columns);
|
||||
|
||||
this.setState({ isAllColumnsEditable: resolveReferences(value) });
|
||||
|
||||
|
|
@ -502,37 +498,37 @@ class TableComponent extends React.Component {
|
|||
paramUpdated({ name: 'displaySearchBox' }, 'value', true, 'properties');
|
||||
const displaySearchBox = component.component.definition.properties.displaySearchBox.value;
|
||||
const displayServerSideFilter = component.component.definition.properties.showFilterButton?.value
|
||||
? resolveReferences(component.component.definition.properties.showFilterButton?.value, currentState)
|
||||
? resolveReferences(component.component.definition.properties.showFilterButton?.value)
|
||||
: false;
|
||||
const displayServerSideSearch = component.component.definition.properties.displaySearchBox?.value
|
||||
? resolveReferences(component.component.definition.properties.displaySearchBox?.value, currentState)
|
||||
? resolveReferences(component.component.definition.properties.displaySearchBox?.value)
|
||||
: false;
|
||||
const serverSidePagination = component.component.definition.properties.serverSidePagination?.value
|
||||
? resolveReferences(component.component.definition.properties.serverSidePagination?.value, currentState)
|
||||
? resolveReferences(component.component.definition.properties.serverSidePagination?.value)
|
||||
: false;
|
||||
|
||||
const clientSidePagination = component.component.definition.properties.clientSidePagination?.value
|
||||
? resolveReferences(component.component.definition.properties.clientSidePagination?.value, currentState)
|
||||
? resolveReferences(component.component.definition.properties.clientSidePagination?.value)
|
||||
: false;
|
||||
|
||||
let enablePagination = !has(component.component.definition.properties, 'enablePagination')
|
||||
? clientSidePagination || serverSidePagination
|
||||
: resolveReferences(component.component.definition.properties.enablePagination?.value, currentState);
|
||||
: resolveReferences(component.component.definition.properties.enablePagination?.value);
|
||||
|
||||
const enabledSort = component.component.definition.properties.enabledSort?.value
|
||||
? resolveReferences(component.component.definition.properties.enabledSort?.value, currentState)
|
||||
? resolveReferences(component.component.definition.properties.enabledSort?.value)
|
||||
: true;
|
||||
const useDynamicColumn = component.component.definition.properties.useDynamicColumn?.value
|
||||
? resolveReferences(component.component.definition.properties.useDynamicColumn?.value, currentState) ?? false
|
||||
? resolveReferences(component.component.definition.properties.useDynamicColumn?.value) ?? false
|
||||
: false;
|
||||
//from app definition values are of string data type if defined or else,undefined
|
||||
const allowSelection = component.component.definition.properties?.allowSelection?.value
|
||||
? resolveReferences(component.component.definition.properties.allowSelection?.value, currentState)
|
||||
: resolveReferences(component.component.definition.properties.highlightSelectedRow.value, currentState) ||
|
||||
resolveReferences(component.component.definition.properties.showBulkSelector.value, currentState);
|
||||
? resolveReferences(component.component.definition.properties.allowSelection?.value)
|
||||
: resolveReferences(component.component.definition.properties.highlightSelectedRow.value) ||
|
||||
resolveReferences(component.component.definition.properties.showBulkSelector.value);
|
||||
|
||||
const renderCustomElement = (param, paramType = 'properties') => {
|
||||
return renderElement(component, componentMeta, paramUpdated, dataQueries, param, paramType, currentState);
|
||||
return renderElement(component, componentMeta, paramUpdated, dataQueries, param, paramType);
|
||||
};
|
||||
|
||||
let items = [];
|
||||
|
|
@ -569,8 +565,8 @@ class TableComponent extends React.Component {
|
|||
{({ innerRef, droppableProps, placeholder }) => (
|
||||
<div className="w-100 d-flex custom-gap-4 flex-column" {...droppableProps} ref={innerRef}>
|
||||
{columns.value.map((item, index) => {
|
||||
const resolvedItemName = resolveReferences(item.name, this.state.currentState);
|
||||
const isEditable = resolveReferences(item.isEditable, this.state.currentState);
|
||||
const resolvedItemName = resolveReferences(item.name);
|
||||
const isEditable = resolveReferences(item.isEditable);
|
||||
const columnVisibility = item?.columnVisibility ?? true;
|
||||
const getSecondaryText = (text) => {
|
||||
switch (text) {
|
||||
|
|
@ -662,7 +658,7 @@ class TableComponent extends React.Component {
|
|||
deleteIconOutsideMenu={true}
|
||||
showCopyColumnOption={true}
|
||||
showVisibilityIcon={true}
|
||||
isColumnVisible={resolveReferences(columnVisibility, this.state.currentState)}
|
||||
isColumnVisible={resolveReferences(columnVisibility)}
|
||||
className={`${
|
||||
this.state.activeColumnPopoverIndex === index && 'active-column-list'
|
||||
}`}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const getTableDefinitionInitialValue = (
|
|||
{ component: { component: { definition: { properties } = {} } = {} } = {} },
|
||||
currentState
|
||||
) => {
|
||||
const resolveProperty = (propertyName) => resolveReferences(properties?.[propertyName]?.value, currentState);
|
||||
const resolveProperty = (propertyName) => resolveReferences(properties?.[propertyName]?.value);
|
||||
|
||||
switch (param) {
|
||||
case 'enablePagination':
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { diff } from 'deep-object-diff';
|
|||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
import { handleLowPriorityWork } from '@/_helpers/editorHelpers';
|
||||
import { appService } from '@/_services';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export const EventManager = ({
|
||||
sourceId,
|
||||
|
|
@ -264,6 +265,9 @@ export const EventManager = ({
|
|||
|
||||
function getPageOptions(event) {
|
||||
// If disabled page is already selected then don't remove from page options
|
||||
|
||||
if (!Array.isArray(pages) || pages.length === 0) return [];
|
||||
|
||||
if (pages.find((page) => page.id === event.pageId)?.disabled) {
|
||||
return pages.map((page) => ({
|
||||
name: page.name,
|
||||
|
|
@ -279,7 +283,7 @@ export const EventManager = ({
|
|||
}
|
||||
|
||||
function handleQueryChange(index, updates) {
|
||||
let newEvents = _.cloneDeep(events);
|
||||
let newEvents = deepClone(events);
|
||||
let updatedEvent = newEvents[index];
|
||||
|
||||
updatedEvent.event = {
|
||||
|
|
@ -301,7 +305,7 @@ export const EventManager = ({
|
|||
}
|
||||
|
||||
function handlerChanged(index, param, value) {
|
||||
let newEvents = _.cloneDeep(events);
|
||||
let newEvents = deepClone(events);
|
||||
|
||||
let updatedEvent = newEvents[index];
|
||||
updatedEvent.event[param] = value;
|
||||
|
|
@ -341,7 +345,7 @@ export const EventManager = ({
|
|||
}
|
||||
|
||||
function removeHandler(index) {
|
||||
const eventsHandler = _.cloneDeep(events);
|
||||
const eventsHandler = deepClone(events);
|
||||
|
||||
const eventId = eventsHandler[index].id;
|
||||
setEventToDeleteLoaderIndex(index);
|
||||
|
|
@ -520,7 +524,7 @@ export const EventManager = ({
|
|||
|
||||
{event.actionId === 'go-to-app' && (
|
||||
<GotoApp
|
||||
event={_.cloneDeep(event)}
|
||||
event={deepClone(event)}
|
||||
handlerChanged={handlerChanged}
|
||||
eventIndex={index}
|
||||
getAllApps={getAllApps}
|
||||
|
|
@ -823,7 +827,7 @@ export const EventManager = ({
|
|||
)}
|
||||
{event.actionId === 'switch-page' && (
|
||||
<SwitchPage
|
||||
event={_.cloneDeep(event)}
|
||||
event={deepClone(event)}
|
||||
handlerChanged={handlerChanged}
|
||||
eventIndex={index}
|
||||
getPages={() => getPageOptions(event)}
|
||||
|
|
@ -940,7 +944,7 @@ export const EventManager = ({
|
|||
}
|
||||
|
||||
const reorderEvents = (startIndex, endIndex) => {
|
||||
const result = _.cloneDeep(events);
|
||||
const result = deepClone(events);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import Copy from '@/_ui/Icon/solidIcons/Copy';
|
|||
import Trash from '@/_ui/Icon/solidIcons/Trash';
|
||||
import classNames from 'classnames';
|
||||
import { useEditorStore, EMPTY_ARRAY } from '@/_stores/editorStore';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
const INSPECTOR_HEADER_OPTIONS = [
|
||||
{
|
||||
|
|
@ -169,7 +170,7 @@ export const Inspector = ({
|
|||
|
||||
function paramUpdated(param, attr, value, paramType, isParamFromTableColumn = false) {
|
||||
let newComponent = JSON.parse(JSON.stringify(component));
|
||||
let newDefinition = _.cloneDeep(newComponent.component.definition);
|
||||
let newDefinition = deepClone(newComponent.component.definition);
|
||||
let allParams = newDefinition[paramType] || {};
|
||||
const paramObject = allParams[param.name];
|
||||
if (!paramObject) {
|
||||
|
|
@ -180,11 +181,7 @@ export const Inspector = ({
|
|||
const defaultValue = getDefaultValue(value);
|
||||
// This is needed to have enable pagination in Table as backward compatible
|
||||
// Whenever enable pagination is false, we turn client and server side pagination as false
|
||||
if (
|
||||
component.component.component === 'Table' &&
|
||||
param.name === 'enablePagination' &&
|
||||
!resolveReferences(value, currentState)
|
||||
) {
|
||||
if (component.component.component === 'Table' && param.name === 'enablePagination' && !resolveReferences(value)) {
|
||||
if (allParams?.['clientSidePagination']?.[attr]) {
|
||||
allParams['clientSidePagination'][attr] = value;
|
||||
}
|
||||
|
|
@ -218,7 +215,7 @@ export const Inspector = ({
|
|||
if (
|
||||
component.component.component === 'Table' &&
|
||||
param.name === 'contentWrap' &&
|
||||
!resolveReferences(value, currentState) &&
|
||||
!resolveReferences(value) &&
|
||||
newDefinition.properties.columns.value.some((item) => item.columnType === 'image' && item.height !== '')
|
||||
) {
|
||||
const updatedColumns = newDefinition.properties.columns.value.map((item) => {
|
||||
|
|
@ -587,11 +584,11 @@ const resolveConditionalStyle = (definition, condition, currentState) => {
|
|||
if (conditionExistsInDefinition) {
|
||||
switch (condition) {
|
||||
case 'cellSize': {
|
||||
const cellSize = resolveReferences(definition[condition]?.value ?? false, currentState) === 'hugContent';
|
||||
const cellSize = resolveReferences(definition[condition]?.value ?? false) === 'hugContent';
|
||||
return cellSize;
|
||||
}
|
||||
default:
|
||||
return resolveReferences(definition[condition]?.value ?? false, currentState);
|
||||
return resolveReferences(definition[condition]?.value ?? false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export function renderCustomStyles(
|
|||
const { conditionallyRender = null } = paramConfig;
|
||||
|
||||
const getResolvedValue = (key) => {
|
||||
return paramTypeDefinition?.[key] && resolveReferences(paramTypeDefinition?.[key], currentState);
|
||||
return paramTypeDefinition?.[key] && resolveReferences(paramTypeDefinition?.[key]);
|
||||
};
|
||||
|
||||
const utilFuncForMultipleChecks = (conditionallyRender) => {
|
||||
|
|
@ -139,7 +139,7 @@ export function renderElement(
|
|||
if (conditionallyRender) {
|
||||
const { key, value } = conditionallyRender;
|
||||
if (paramTypeDefinition?.[key] ?? value) {
|
||||
const resolvedValue = paramTypeDefinition?.[key] && resolveReferences(paramTypeDefinition?.[key], currentState);
|
||||
const resolvedValue = paramTypeDefinition?.[key] && resolveReferences(paramTypeDefinition?.[key]);
|
||||
if (resolvedValue?.value !== value) return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,20 +60,8 @@ export const LeftSidebarInspector = ({
|
|||
}, [selectedComponents]);
|
||||
|
||||
const memoizedJSONData = React.useMemo(() => {
|
||||
const updatedQueries = {};
|
||||
const { queries: currentQueries } = currentState;
|
||||
// if (!_.isEmpty(dataQueries)) {
|
||||
// const copyCurrentQueies = JSON.parse(JSON.stringify(currentQueries));
|
||||
// dataQueries.forEach((query) => {
|
||||
// updatedQueries[query.name] = _.merge(copyCurrentQueies[query.name], {
|
||||
// id: query.id,
|
||||
// isLoading: false,
|
||||
// data: [],
|
||||
// rawData: [],
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// const data = _.merge(currentState, { queries: updatedQueries });
|
||||
|
||||
const jsontreeData = { ...currentState, queries: currentQueries };
|
||||
delete jsontreeData.errors;
|
||||
delete jsontreeData.client;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { useCurrentState } from '@/_stores/currentStateStore';
|
|||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
const LeftSidebarPageSelector = ({
|
||||
appDefinition,
|
||||
|
|
@ -36,7 +37,7 @@ const LeftSidebarPageSelector = ({
|
|||
}) => {
|
||||
const pages = useMemo(
|
||||
() =>
|
||||
Object.entries(_.cloneDeep(appDefinition.pages))
|
||||
Object.entries(deepClone(appDefinition.pages))
|
||||
.map(([id, page]) => ({ id, ...page }))
|
||||
.sort((a, b) => a.index - b.index) || [],
|
||||
[JSON.stringify(appDefinition.pages)]
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { shallow } from 'zustand/shallow';
|
|||
import useDebugger from './SidebarDebugger/useDebugger';
|
||||
import { GlobalSettings } from '../Header/GlobalSettings';
|
||||
import cx from 'classnames';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export const LeftSidebar = forwardRef((props, ref) => {
|
||||
const router = useRouter();
|
||||
|
|
@ -159,7 +160,7 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
updatePageHandle={updatePageHandle}
|
||||
clonePage={clonePage}
|
||||
pages={
|
||||
Object.entries(_.cloneDeep(appDefinition).pages)
|
||||
Object.entries(deepClone(appDefinition).pages)
|
||||
.map(([id, page]) => ({ id, ...page }))
|
||||
.sort((a, b) => a.index - b.index) || []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { validateProperties } from '../component-properties-validation';
|
|||
import { getComponentName, debuggerActions } from '@/_helpers/appUtils';
|
||||
import { memoizeFunction } from '../../_helpers/editorHelpers';
|
||||
import { componentTypes } from '../WidgetManager/components';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { useCurrentStateStore } from '@/_stores/currentStateStore';
|
||||
|
||||
const shouldAddBoxShadowAndVisibility = ['TextInput', 'PasswordInput', 'NumberInput', 'Text'];
|
||||
|
||||
|
|
@ -18,26 +18,15 @@ const getComponentMetaData = memoizeFunction((componentType) => {
|
|||
});
|
||||
|
||||
const HydrateWithResolveReferences = ({ id, mode, component, customResolvables, children }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const componentMeta = useMemo(() => getComponentMetaData(component?.component), []);
|
||||
|
||||
const currentState = useCurrentState();
|
||||
const resolvedProperties = resolveProperties(component, {}, null, customResolvables, id);
|
||||
|
||||
const resolvedProperties = useMemo(() => {
|
||||
return resolveProperties(component, currentState, null, customResolvables, id);
|
||||
}, [component, currentState, customResolvables, id]);
|
||||
const resolvedStyles = resolveStyles(component, {}, null, customResolvables);
|
||||
|
||||
const resolvedStyles = useMemo(() => {
|
||||
return resolveStyles(component, currentState, null, customResolvables);
|
||||
}, [component, currentState, customResolvables]);
|
||||
const resolvedGeneralProperties = resolveGeneralProperties(component, {}, null, customResolvables);
|
||||
|
||||
const resolvedGeneralProperties = useMemo(() => {
|
||||
return resolveGeneralProperties(component, currentState, null, customResolvables);
|
||||
}, [component, currentState, customResolvables]);
|
||||
|
||||
const resolvedGeneralStyles = useMemo(() => {
|
||||
return resolveGeneralStyles(component, currentState, null, customResolvables);
|
||||
}, [component, currentState, customResolvables]);
|
||||
const resolvedGeneralStyles = resolveGeneralStyles(component, {}, null, customResolvables);
|
||||
|
||||
const [validatedProperties, propertyErrors] =
|
||||
mode === 'edit' && component.validate
|
||||
|
|
@ -67,6 +56,11 @@ const HydrateWithResolveReferences = ({ id, mode, component, customResolvables,
|
|||
: [resolvedGeneralStyles, []];
|
||||
|
||||
useEffect(() => {
|
||||
const isEditorReady = useCurrentStateStore.getState().isEditorReady;
|
||||
|
||||
if (!isEditorReady) return;
|
||||
const currentState = useCurrentStateStore.getState();
|
||||
|
||||
const currentPage = currentState?.page;
|
||||
const componentName = getComponentName(currentState, id);
|
||||
const errorLog = Object.fromEntries(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import PlusRectangle from '@/_ui/Icon/solidIcons/PlusRectangle';
|
|||
import Remove from '@/_ui/Icon/bulkIcons/Remove';
|
||||
import ParameterForm from './ParameterForm';
|
||||
|
||||
const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRemove, currentState, otherParams }) => {
|
||||
const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRemove, otherParams }) => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const closeMenu = () => setShowModal(false);
|
||||
|
||||
|
|
@ -66,7 +66,6 @@ const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRe
|
|||
defaultValue={defaultValue}
|
||||
onSubmit={handleSubmit}
|
||||
showModal={showModal}
|
||||
currentState={currentState}
|
||||
/>
|
||||
</Popover>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ const ParameterList = ({
|
|||
handleAddParameter,
|
||||
handleParameterChange,
|
||||
handleParameterRemove,
|
||||
currentState,
|
||||
darkMode,
|
||||
containerRef,
|
||||
}) => {
|
||||
|
|
@ -68,7 +67,7 @@ const ParameterList = ({
|
|||
name={parameter.name}
|
||||
otherParams={formattedParameters.filter((p) => p.name !== parameter.name)}
|
||||
defaultValue={parameter.defaultValue}
|
||||
currentState={currentState}
|
||||
// currentState={currentState}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
);
|
||||
|
|
@ -97,7 +96,6 @@ const ParameterList = ({
|
|||
handleParameterChange(selectedParameter.index, param);
|
||||
setSelectedParameter();
|
||||
}}
|
||||
currentState={currentState}
|
||||
showModal={showMore}
|
||||
/>
|
||||
) : (
|
||||
|
|
@ -136,12 +134,7 @@ const ParameterList = ({
|
|||
)}
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
<ParameterDetails
|
||||
onSubmit={handleAddParameter}
|
||||
currentState={currentState}
|
||||
darkMode={darkMode}
|
||||
otherParams={formattedParameters}
|
||||
/>
|
||||
<ParameterDetails onSubmit={handleAddParameter} darkMode={darkMode} otherParams={formattedParameters} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import cx from 'classnames';
|
||||
import { cloneDeep, isEmpty } from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
import { allSources, source } from '../QueryEditors';
|
||||
|
|
@ -19,6 +19,7 @@ import { useSelectedQuery, useSelectedDataSource } from '@/_stores/queryPanelSto
|
|||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import SuccessNotificationInputs from './SuccessNotificationInputs';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export const QueryManagerBody = ({
|
||||
darkMode,
|
||||
|
|
@ -87,7 +88,7 @@ export const QueryManagerBody = ({
|
|||
const updatedOptions = cleanFocusedFields(newOptions);
|
||||
setOptions((options) => ({ ...options, ...updatedOptions }));
|
||||
|
||||
updateDataQuery(cloneDeep({ ...options, ...updatedOptions }));
|
||||
updateDataQuery(deepClone({ ...options, ...updatedOptions }));
|
||||
};
|
||||
|
||||
const optionchanged = (option, value) => {
|
||||
|
|
|
|||
|
|
@ -15,15 +15,14 @@ import {
|
|||
useShowCreateQuery,
|
||||
useNameInputFocussed,
|
||||
} from '@/_stores/queryPanelStore';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { useSelectedQueryLoadingState } from '@/_stores/currentStateStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import ParameterList from './ParameterList';
|
||||
import { decodeEntities } from '@/_helpers/utils';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, setOptions }, ref) => {
|
||||
const { renameQuery } = useDataQueriesActions();
|
||||
|
|
@ -31,8 +30,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, se
|
|||
const selectedDataSource = useSelectedDataSource();
|
||||
const [showCreateQuery, setShowCreateQuery] = useShowCreateQuery();
|
||||
const queryName = selectedQuery?.name ?? '';
|
||||
const currentState = useCurrentState((state) => ({ queries: state.queries }), shallow);
|
||||
const { queries } = currentState;
|
||||
const isLoading = useSelectedQueryLoadingState();
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
|
|
@ -97,7 +95,6 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, se
|
|||
};
|
||||
|
||||
const renderRunButton = () => {
|
||||
const { isLoading } = queries[selectedQuery?.name] ?? false;
|
||||
return (
|
||||
<span
|
||||
{...(isInDraft && {
|
||||
|
|
@ -131,7 +128,6 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, se
|
|||
|
||||
const renderButtons = () => {
|
||||
if (selectedQuery === null || showCreateQuery) return;
|
||||
const { isLoading } = queries[selectedQuery?.name] ?? false;
|
||||
return (
|
||||
<>
|
||||
<PreviewButton
|
||||
|
|
@ -146,7 +142,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, se
|
|||
|
||||
const optionsChanged = (newOptions) => {
|
||||
setOptions(newOptions);
|
||||
updateDataQuery(cloneDeep(newOptions));
|
||||
updateDataQuery(deepClone(newOptions));
|
||||
};
|
||||
|
||||
const handleAddParameter = (newParameter) => {
|
||||
|
|
@ -201,7 +197,6 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, se
|
|||
handleAddParameter={handleAddParameter}
|
||||
handleParameterChange={handleParameterChange}
|
||||
handleParameterRemove={handleParameterRemove}
|
||||
currentState={currentState}
|
||||
darkMode={darkMode}
|
||||
containerRef={paramListContainerRef}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -181,7 +181,6 @@ class Restapi extends React.Component {
|
|||
>
|
||||
<CodeHinter
|
||||
type="basic"
|
||||
currentState={this.props.currentState}
|
||||
initialValue={options.url}
|
||||
onChange={(value) => {
|
||||
changeOption(this, 'url', value);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,13 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { defaults } from 'lodash';
|
||||
import { Card } from 'react-bootstrap';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import ParameterList from '../../Components/ParameterList';
|
||||
import CodeHinter from '@/Editor/CodeEditor';
|
||||
|
||||
const Runjs = (props) => {
|
||||
const currentState = useCurrentState();
|
||||
const [currStateForCodeHinter, setCurrStateForCodeHinter] = useState(currentState);
|
||||
const initialOptions = defaults({ ...props.options }, { code: '//Type your JavaScript code here' });
|
||||
const [options, setOptions] = useState(initialOptions);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrStateForCodeHinter({
|
||||
...currentState,
|
||||
parameters: options?.parameters?.reduce((params, param) => ({ ...params, [param.name]: param.defaultValue }), {}),
|
||||
});
|
||||
}, [currentState?.components, options?.parameters]);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(props.options);
|
||||
}, [props.options]);
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ import Remove from '@/_ui/Icon/solidIcons/Remove';
|
|||
import Information from '@/_ui/Icon/solidIcons/Information';
|
||||
import Icon from '@/_ui/Icon/solidIcons/index';
|
||||
import set from 'lodash/set';
|
||||
import { cloneDeep, isEmpty } from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { getPrivateRoute } from '@/_helpers/routes';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useConfirm from './Confirm';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
const JoinConstraint = ({ darkMode, index, onRemove, onChange, data }) => {
|
||||
const { selectedTableId, tables, joinOptions, findTableDetails, tableForeignKeyInfo } =
|
||||
|
|
@ -134,7 +135,7 @@ const JoinConstraint = ({ darkMode, index, onRemove, onChange, data }) => {
|
|||
const adjacentTableForeignKeyDetails = checkIfAdjacentTableHasForeignKey(isChoosingLHStable, tableId);
|
||||
if (isChoosingLHStable) {
|
||||
if (adjacentTableForeignKeyDetails.length) {
|
||||
const newData = cloneDeep({ ...data });
|
||||
const newData = deepClone({ ...data });
|
||||
const newConditionsList = adjacentTableForeignKeyDetails.map((adjacentTableForeignKey) => {
|
||||
const { referenced_column_names = [], column_names = [] } = adjacentTableForeignKey;
|
||||
const newCondition = {
|
||||
|
|
@ -156,7 +157,7 @@ const JoinConstraint = ({ darkMode, index, onRemove, onChange, data }) => {
|
|||
set(newData, 'conditions.conditionsList', newConditionsList);
|
||||
onChange(newData);
|
||||
} else {
|
||||
const newData = cloneDeep({ ...data });
|
||||
const newData = deepClone({ ...data });
|
||||
const { conditionsList = [{}] } = newData?.conditions || {};
|
||||
const newConditionsList = conditionsList.map((condition) => {
|
||||
const newCondition = { ...condition };
|
||||
|
|
@ -170,7 +171,7 @@ const JoinConstraint = ({ darkMode, index, onRemove, onChange, data }) => {
|
|||
}
|
||||
} else {
|
||||
if (adjacentTableForeignKeyDetails.length) {
|
||||
const newData = cloneDeep({ ...data });
|
||||
const newData = deepClone({ ...data });
|
||||
const newConditionsList = adjacentTableForeignKeyDetails.map((adjacentTableForeignKey) => {
|
||||
const { referenced_column_names = [], column_names = [] } = adjacentTableForeignKey;
|
||||
const newCondition = {
|
||||
|
|
@ -193,7 +194,7 @@ const JoinConstraint = ({ darkMode, index, onRemove, onChange, data }) => {
|
|||
set(newData, 'table', tableId);
|
||||
onChange(newData);
|
||||
} else {
|
||||
const newData = cloneDeep({ ...data });
|
||||
const newData = deepClone({ ...data });
|
||||
const { conditionsList = [] } = newData?.conditions || {};
|
||||
const newConditionsList = conditionsList.map((condition) => {
|
||||
const newCondition = { ...condition };
|
||||
|
|
@ -341,7 +342,7 @@ const JoinConstraint = ({ darkMode, index, onRemove, onChange, data }) => {
|
|||
index={index}
|
||||
groupOperator={operator}
|
||||
onOperatorChange={(value) => {
|
||||
const newData = cloneDeep(data);
|
||||
const newData = deepClone(data);
|
||||
set(newData, 'conditions.operator', value);
|
||||
onChange(newData);
|
||||
}}
|
||||
|
|
@ -352,13 +353,13 @@ const JoinConstraint = ({ darkMode, index, onRemove, onChange, data }) => {
|
|||
}
|
||||
return con;
|
||||
});
|
||||
const newData = cloneDeep(data);
|
||||
const newData = deepClone(data);
|
||||
set(newData, 'conditions.conditionsList', newConditionsList);
|
||||
onChange(newData);
|
||||
}}
|
||||
onRemove={() => {
|
||||
const newConditionsList = conditionsList.filter((_cond, i) => i !== index);
|
||||
const newData = cloneDeep(data);
|
||||
const newData = deepClone(data);
|
||||
set(newData, 'conditions.conditionsList', newConditionsList);
|
||||
onChange(newData);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ import React, { useContext } from 'react';
|
|||
import { Col, Container, Row } from 'react-bootstrap';
|
||||
import { TooljetDatabaseContext } from '@/TooljetDatabase/index';
|
||||
import DropDownSelect from './DropDownSelect';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export default function JoinSelect({ darkMode }) {
|
||||
const { joinOptions, tableInfo, joinTableOptions, joinTableOptionsChange, findTableDetails } =
|
||||
useContext(TooljetDatabaseContext);
|
||||
|
||||
const joinSelectOptions = cloneDeep(joinTableOptions['fields']) || [];
|
||||
const joinSelectOptions = deepClone(joinTableOptions['fields']) || [];
|
||||
const setJoinSelectOptions = (fields) => {
|
||||
joinTableOptionsChange('fields', fields);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,18 +9,17 @@ import { DeleteRows } from './DeleteRows';
|
|||
import { toast } from 'react-hot-toast';
|
||||
import { queryManagerSelectComponentStyle } from '@/_ui/Select/styles';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { JoinTable } from './JoinTable';
|
||||
import { cloneDeep, difference } from 'lodash';
|
||||
import { difference } from 'lodash';
|
||||
import DropDownSelect from './DropDownSelect';
|
||||
import { getPrivateRoute } from '@/_helpers/routes';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLayout }) => {
|
||||
const computeSelectStyles = (darkMode, width) => {
|
||||
return queryManagerSelectComponentStyle(darkMode, width);
|
||||
};
|
||||
const currentState = useCurrentState();
|
||||
const navigate = useNavigate();
|
||||
const { current_organization_id: organizationId } = authenticationService.currentSessionValue;
|
||||
const mounted = useMounted();
|
||||
|
|
@ -64,7 +63,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
|
|||
|
||||
setJoinTableOptions((prevJoinOptions) => {
|
||||
const { conditions, order_by = [], joins: currJoins, fields: currFields = [] } = prevJoinOptions;
|
||||
const conditionsList = cloneDeep(conditions?.conditionsList || []);
|
||||
const conditionsList = deepClone(conditions?.conditionsList || []);
|
||||
const newConditionsList = conditionsList.filter((condition) => {
|
||||
const { leftField } = condition || {};
|
||||
if (tableSet.has(leftField?.table)) {
|
||||
|
|
@ -244,7 +243,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
|
|||
if (isNewTableAdded) {
|
||||
setJoinTableOptions((joinOptions) => {
|
||||
const { fields } = joinOptions;
|
||||
const newFields = cloneDeep(fields).filter((field) => field.table !== tableId);
|
||||
const newFields = deepClone(fields).filter((field) => field.table !== tableId);
|
||||
newFields.push(
|
||||
...(data?.result?.columns
|
||||
? data.result.columns.map((col) => ({
|
||||
|
|
@ -393,7 +392,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
|
|||
if (isNewTableAdded) {
|
||||
setJoinTableOptions((joinOptions) => {
|
||||
const { fields } = joinOptions;
|
||||
const newFields = cloneDeep(fields).filter((field) => field.table !== tableId);
|
||||
const newFields = deepClone(fields).filter((field) => field.table !== tableId);
|
||||
newFields.push(
|
||||
...(data?.result?.columns
|
||||
? data.result.columns.map((col) => ({
|
||||
|
|
@ -525,14 +524,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
|
|||
</div>
|
||||
|
||||
{/* component to render based on the operation */}
|
||||
{ComponentToRender && (
|
||||
<ComponentToRender
|
||||
currentState={currentState}
|
||||
options={options}
|
||||
optionchanged={optionchanged}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
)}
|
||||
{ComponentToRender && <ComponentToRender options={options} optionchanged={optionchanged} darkMode={darkMode} />}
|
||||
</TooljetDatabaseContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ const QueryManager = ({ mode, appId, darkMode, apps, allComponents, appDefinitio
|
|||
parameters: selectedQuery?.options?.parameters?.reduce(
|
||||
(parameters, parameter) => ({
|
||||
...parameters,
|
||||
[parameter.name]: resolveReferences(parameter.defaultValue, {}, undefined),
|
||||
[parameter.name]: resolveReferences(parameter.defaultValue, undefined),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ import useWindowResize from '@/_hooks/useWindowResize';
|
|||
import { useQueryPanelActions, useQueryPanelStore } from '@/_stores/queryPanelStore';
|
||||
import { useDataQueriesStore, useDataQueries } from '@/_stores/dataQueriesStore';
|
||||
import Maximize from '@/_ui/Icon/solidIcons/Maximize';
|
||||
import { cloneDeep, isEmpty, isEqual } from 'lodash';
|
||||
import { isEmpty, isEqual } from 'lodash';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import cx from 'classnames';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
const QueryPanel = ({
|
||||
dataQueriesChanged,
|
||||
|
|
@ -54,10 +55,10 @@ const QueryPanel = ({
|
|||
}
|
||||
|
||||
//removing updated_at since this value changes whenever the data is updated in the BE
|
||||
const formattedQuery = cloneDeep(selectedQuery);
|
||||
const formattedQuery = deepClone(selectedQuery);
|
||||
delete formattedQuery.updated_at;
|
||||
|
||||
const formattedPrevQuery = cloneDeep(prevState?.selectedQuery || {});
|
||||
const formattedPrevQuery = deepClone(prevState?.selectedQuery || {});
|
||||
delete formattedPrevQuery.updated_at;
|
||||
|
||||
if (!isEqual(formattedQuery, formattedPrevQuery)) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
import { resolveWidgetFieldValue } from '@/_helpers/utils';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { restrictedWidgetsObj } from '@/Editor/WidgetManager/restrictedWidgetsConfig';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { getCurrentState } from '@/_stores/currentStateStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
|
|
@ -24,6 +24,7 @@ import { useEditorStore } from '@/_stores/editorStore';
|
|||
import { diff } from 'deep-object-diff';
|
||||
import { useGridStore, useResizingComponentId } from '@/_stores/gridStore';
|
||||
import GhostWidget from './GhostWidget';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export const SubContainer = ({
|
||||
mode,
|
||||
|
|
@ -58,7 +59,6 @@ export const SubContainer = ({
|
|||
}) => {
|
||||
const appDefinition = useEditorStore((state) => state.appDefinition, shallow);
|
||||
|
||||
const currentState = useCurrentState();
|
||||
const { selectedComponents } = useEditorStore(
|
||||
(state) => ({
|
||||
selectedComponents: state.selectedComponents,
|
||||
|
|
@ -190,22 +190,24 @@ export const SubContainer = ({
|
|||
}, [containerWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
if (appDefinitionChanged) {
|
||||
const definition = useEditorStore.getState().appDefinition;
|
||||
|
||||
if (definition) {
|
||||
const newDefinition = {
|
||||
...appDefinition,
|
||||
...definition,
|
||||
pages: {
|
||||
...appDefinition.pages,
|
||||
...definition.pages,
|
||||
[currentPageId]: {
|
||||
...appDefinition.pages[currentPageId],
|
||||
...definition.pages[currentPageId],
|
||||
components: {
|
||||
...appDefinition.pages[currentPageId].components,
|
||||
...definition.pages[currentPageId].components,
|
||||
...childWidgets,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const oldComponents = appDefinition.pages[currentPageId]?.components ?? {};
|
||||
const oldComponents = definition.pages[currentPageId]?.components ?? {};
|
||||
const newComponents = newDefinition.pages[currentPageId]?.components ?? {};
|
||||
|
||||
const componendAdded = Object.keys(newComponents).length > Object.keys(oldComponents).length;
|
||||
|
|
@ -216,7 +218,7 @@ export const SubContainer = ({
|
|||
opts.componentAdded = true;
|
||||
}
|
||||
|
||||
const shouldUpdate = !_.isEmpty(diff(appDefinition, newDefinition));
|
||||
const shouldUpdate = !_.isEmpty(diff(definition, newDefinition));
|
||||
|
||||
if (shouldUpdate) {
|
||||
appDefinitionChanged(newDefinition, opts);
|
||||
|
|
@ -241,7 +243,7 @@ export const SubContainer = ({
|
|||
return;
|
||||
}
|
||||
|
||||
const componentMeta = _.cloneDeep(
|
||||
const componentMeta = deepClone(
|
||||
componentTypes.find((component) => component.component === item.component.component)
|
||||
);
|
||||
const canvasBoundingRect = parentRef.current.getElementsByClassName('real-canvas')[0].getBoundingClientRect();
|
||||
|
|
@ -280,14 +282,14 @@ export const SubContainer = ({
|
|||
Listview: 'listItem',
|
||||
});
|
||||
const customResolverVariable = widgetResolvables[parentMeta?.component];
|
||||
const defaultChildren = _.cloneDeep(parentMeta)['defaultChildren'];
|
||||
const defaultChildren = deepClone(parentMeta)['defaultChildren'];
|
||||
const parentId = newComponent.id;
|
||||
|
||||
defaultChildren.forEach((child) => {
|
||||
const { componentName, layout, incrementWidth, properties, accessorKey, tab, defaultValue, styles } =
|
||||
child;
|
||||
|
||||
const componentMeta = _.cloneDeep(
|
||||
const componentMeta = deepClone(
|
||||
componentTypes.find((component) => component.component === componentName)
|
||||
);
|
||||
const componentData = JSON.parse(JSON.stringify(componentMeta));
|
||||
|
|
@ -530,6 +532,7 @@ export const SubContainer = ({
|
|||
}
|
||||
|
||||
const getContainerProps = (componentId) => {
|
||||
const currentState = getCurrentState();
|
||||
return {
|
||||
mode,
|
||||
snapToGrid,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
import queryString from 'query-string';
|
||||
import ViewerLogoIcon from './Icons/viewer-logo.svg';
|
||||
import { DataSourceTypes } from './DataSourceManager/SourceComponents';
|
||||
import { resolveReferences, isQueryRunnable, setWindowTitle, pageTitles, isValidUUID } from '@/_helpers/utils';
|
||||
import { resolveReferences, isQueryRunnable, isValidUUID } from '@/_helpers/utils';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import _ from 'lodash';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
|
@ -50,6 +50,8 @@ import { findAllEntityReferences } from '@/_stores/utils';
|
|||
import { dfs } from '@/_stores/handleReferenceTransactions';
|
||||
import useAppDarkMode from '@/_hooks/useAppDarkMode';
|
||||
import TooljetBanner from './Viewer/TooljetBanner';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
import { fetchAndSetWindowTitle, pageTitles } from '@white-label/whiteLabelling';
|
||||
|
||||
class ViewerComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -491,7 +493,7 @@ class ViewerComponent extends React.Component {
|
|||
useCurrentStateStore.getState().actions.initializeCurrentStateOnVersionSwitch();
|
||||
this.setStateForApp(data, true);
|
||||
this.setStateForContainer(data);
|
||||
setWindowTitle({
|
||||
fetchAndSetWindowTitle({
|
||||
page: pageTitles.VIEWER,
|
||||
appName: data.name,
|
||||
preview,
|
||||
|
|
@ -516,7 +518,7 @@ class ViewerComponent extends React.Component {
|
|||
await appService
|
||||
.fetchAppByVersion(appId, versionId)
|
||||
.then((data) => {
|
||||
setWindowTitle({
|
||||
fetchAndSetWindowTitle({
|
||||
page: pageTitles.VIEWER,
|
||||
appName: data.name,
|
||||
preview: true,
|
||||
|
|
@ -703,7 +705,7 @@ class ViewerComponent extends React.Component {
|
|||
const bgColor =
|
||||
(this.props.canvasBackground?.backgroundFxQuery || this.props.canvasBackground?.canvasBackgroundColor) ??
|
||||
'#2f3c4c';
|
||||
const resolvedBackgroundColor = resolveReferences(bgColor, this.props.currentState);
|
||||
const resolvedBackgroundColor = resolveReferences(bgColor);
|
||||
if (['#2f3c4c', '#F2F2F5', '#edeff5'].includes(resolvedBackgroundColor)) {
|
||||
return this.props.darkMode ? '#2f3c4c' : '#F2F2F5';
|
||||
}
|
||||
|
|
@ -851,7 +853,7 @@ class ViewerComponent extends React.Component {
|
|||
const queryConfirmationList = this.props?.queryConfirmationList ?? [];
|
||||
const canvasMaxWidth = this.computeCanvasMaxWidth();
|
||||
const pages =
|
||||
Object.entries(_.cloneDeep(appDefinition)?.pages)
|
||||
Object.entries(deepClone(appDefinition)?.pages)
|
||||
.map(([id, page]) => ({ id, ...page }))
|
||||
.sort((a, b) => a.index - b.index) || [];
|
||||
|
||||
|
|
@ -1074,14 +1076,18 @@ const withStore = (Component) => (props) => {
|
|||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const currentComponentsDef = appDefinition?.pages?.[currentPageId]?.components || {};
|
||||
const currentComponents = Object.keys(currentComponentsDef);
|
||||
const isPageSwitched = useResolveStore.getState().isPageSwitched;
|
||||
|
||||
setTimeout(() => {
|
||||
if (currentComponents.length > 0) {
|
||||
batchUpdateComponents(currentComponents);
|
||||
}
|
||||
}, 400);
|
||||
if (isPageSwitched) {
|
||||
const currentComponentsDef = appDefinition?.pages?.[currentPageId]?.components || {};
|
||||
const currentComponents = Object.keys(currentComponentsDef);
|
||||
|
||||
setTimeout(() => {
|
||||
if (currentComponents.length > 0) {
|
||||
batchUpdateComponents(currentComponents);
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPageId]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,17 @@
|
|||
import { resolveReferences } from '@/_helpers/utils';
|
||||
|
||||
import { resolveReferences as newResolveReference } from './CodeEditor/utils';
|
||||
|
||||
const handleResolveReferences = (initialValue, defaultValue, customResolvers) => {
|
||||
const [_, error, value] = newResolveReference(initialValue, {}, customResolvers);
|
||||
|
||||
if (error) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const resolveProperties = (component, currentState, defaultValue, customResolvables) => {
|
||||
if (currentState) {
|
||||
return Object.entries(component.definition.properties).reduce(
|
||||
|
|
@ -8,7 +20,7 @@ export const resolveProperties = (component, currentState, defaultValue, customR
|
|||
...{
|
||||
[entry[0]]: entry[1]?.skipResolve
|
||||
? entry[1].value
|
||||
: resolveReferences(entry[1].value, currentState, defaultValue, customResolvables),
|
||||
: handleResolveReferences(entry[1].value, defaultValue, customResolvables),
|
||||
},
|
||||
}),
|
||||
{}
|
||||
|
|
@ -23,7 +35,7 @@ export const resolveStyles = (component, currentState, defaultValue, customResol
|
|||
const key = entry[0];
|
||||
const value = entry[1]?.skipResolve
|
||||
? entry[1].value
|
||||
: resolveReferences(entry[1].value, currentState, defaultValue, customResolvables);
|
||||
: handleResolveReferences(entry[1].value, defaultValue, customResolvables);
|
||||
return {
|
||||
...resolvedStyles,
|
||||
...{ [key]: value },
|
||||
|
|
@ -41,7 +53,7 @@ export const resolveGeneralProperties = (component, currentState, defaultValue,
|
|||
const key = entry[0];
|
||||
const value = entry[1]?.skipResolve
|
||||
? entry[1].value
|
||||
: resolveReferences(entry[1].value, currentState, defaultValue, customResolvables);
|
||||
: handleResolveReferences(entry[1].value, defaultValue, customResolvables);
|
||||
return {
|
||||
...resolvedGeneral,
|
||||
...{ [key]: value },
|
||||
|
|
@ -59,7 +71,7 @@ export const resolveGeneralStyles = (component, currentState, defaultValue, cust
|
|||
const key = entry[0];
|
||||
const value = entry[1]?.skipResolve
|
||||
? entry[1].value
|
||||
: resolveReferences(entry[1].value, currentState, defaultValue, customResolvables);
|
||||
: handleResolveReferences(entry[1].value, defaultValue, customResolvables);
|
||||
return {
|
||||
...resolvedGeneral,
|
||||
...{ [key]: value },
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const {
|
|||
never,
|
||||
} = require('superstruct');
|
||||
|
||||
import { validateMultilineCode } from '@/_helpers/utility';
|
||||
import _ from 'lodash';
|
||||
|
||||
export const generateSchemaFromValidationDefinition = (definition, recursionDepth = 0) => {
|
||||
|
|
@ -129,17 +130,17 @@ export const validateProperties = (resolvedProperties, propertyDefinitions) => {
|
|||
? any()
|
||||
: generateSchemaFromValidationDefinition(validationDefinition);
|
||||
|
||||
const reservedKeyword = ['app', 'window']; // Case-sensitive reserved keywords
|
||||
const keywordRegex = new RegExp(`\\b(${reservedKeyword.join('|')})\\b`, 'i');
|
||||
const hasReservedkeyword = keywordRegex.test(value);
|
||||
if (typeof value === string && value.startsWith('{{') && value.endsWith('}}')) {
|
||||
const { status, data } = validateMultilineCode(value);
|
||||
|
||||
if (hasReservedkeyword) {
|
||||
allErrors.push({
|
||||
property: propertyDefinitions[propertyName]?.displayName,
|
||||
message: 'Code contains reserved keywords',
|
||||
});
|
||||
if (status === 'failed') {
|
||||
allErrors.push({
|
||||
property: propertyDefinitions[propertyName]?.displayName,
|
||||
message: data,
|
||||
});
|
||||
|
||||
return [propertyName, defaultValue];
|
||||
return [propertyName, defaultValue];
|
||||
}
|
||||
}
|
||||
|
||||
const [_valid, errors, newValue] = propertyName ? validate(value, schema, defaultValue) : [true, []];
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ButtonSolid } from '@/_components/AppButton';
|
|||
import { withTranslation } from 'react-i18next';
|
||||
import EnterIcon from '../../assets/images/onboardingassets/Icons/Enter';
|
||||
import Spinner from '@/_ui/Spinner';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
class ForgotPasswordComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -21,6 +22,7 @@ class ForgotPasswordComponent extends React.Component {
|
|||
};
|
||||
}
|
||||
darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
handleChange = (event) => {
|
||||
this.setState({ [event.target.name]: event.target.value, emailError: '' });
|
||||
|
|
@ -68,9 +70,10 @@ class ForgotPasswordComponent extends React.Component {
|
|||
Forgot Password
|
||||
</h2>
|
||||
<p className="common-auth-sub-header" data-cy="forgot-password-sub-header">
|
||||
New to ToolJet?
|
||||
New to {this.whiteLabelText}?
|
||||
<Link
|
||||
to={'/signup'}
|
||||
state={{ from: '/forgot-password' }}
|
||||
tabIndex="-1"
|
||||
style={{ color: this.darkMode && '#3E63DD' }}
|
||||
data-cy="create-an-account-link"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { SearchBox } from '@/_components';
|
|||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { BreadCrumbContext } from '@/App';
|
||||
import { pageTitles, setWindowTitle } from '@/_helpers/utils';
|
||||
import { fetchAndSetWindowTitle, pageTitles } from '@white-label/whiteLabelling';
|
||||
|
||||
export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasource }) => {
|
||||
const containerRef = useRef(null);
|
||||
|
|
@ -69,7 +69,7 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setWindowTitle({ page: `${selectedDataSource?.name || pageTitles.DATA_SOURCES}` });
|
||||
fetchAndSetWindowTitle({ page: `${selectedDataSource?.name || pageTitles.DATA_SOURCES}` });
|
||||
if (selectedDataSource) {
|
||||
setModalProps({ ...modalProps, backdrop: false });
|
||||
}
|
||||
|
|
@ -328,6 +328,7 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
|
|||
{datasources.map((item) => (
|
||||
<Card
|
||||
key={item.key}
|
||||
darkMode={darkMode}
|
||||
title={item.title}
|
||||
src={item?.src}
|
||||
usePluginIcon={isEmpty(item?.iconFile?.data)}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export const List = ({ updateSelectedDatasource }) => {
|
|||
<EmptyFoldersIllustration />
|
||||
</div>
|
||||
<div className="tj-text-md text-secondary" data-cy="empty-ds-page-text">
|
||||
No datasources added
|
||||
{filteredData?.length === 0 && dataSources?.length !== 0 ? 'No results found' : 'No datasources added'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { GlobalDataSourcesPage } from './GlobalDataSourcesPage';
|
|||
import { toast } from 'react-hot-toast';
|
||||
import { BreadCrumbContext } from '@/App/App';
|
||||
import { DATA_SOURCE_TYPE } from '@/_helpers/constants';
|
||||
import { fetchAndSetWindowTitle, pageTitles } from '@white-label/whiteLabelling';
|
||||
|
||||
export const GlobalDataSourcesContext = createContext({
|
||||
showDataSourceManagerModal: false,
|
||||
|
|
@ -39,6 +40,7 @@ export const GlobalDatasources = (props) => {
|
|||
selectedDataSource
|
||||
? updateSidebarNAV(selectedDataSource.name)
|
||||
: !activeDatasourceList && updateSidebarNAV('Commonly used');
|
||||
fetchAndSetWindowTitle({ page: `${selectedDataSource?.name || pageTitles.DATA_SOURCES}` });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(dataSources), JSON.stringify(selectedDataSource)]);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import EmptyIllustration from '@assets/images/no-apps.svg';
|
|||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import EmptyFoldersIllustration from '@assets/images/icons/no-queries-added.svg';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
export const BlankPage = function BlankPage({
|
||||
readAndImport,
|
||||
|
|
@ -17,6 +18,7 @@ export const BlankPage = function BlankPage({
|
|||
canCreateApp,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
const [deploying, setDeploying] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
|
@ -56,12 +58,17 @@ export const BlankPage = function BlankPage({
|
|||
<div className="row homepage-empty-container">
|
||||
<div className="col-7">
|
||||
<h3 className="empty-welcome-header" data-cy="empty-homepage-welcome-header">
|
||||
{t('blankPage.welcomeToToolJet', 'Welcome to your new ToolJet workspace')}
|
||||
{t('blankPage.welcomeToToolJet', `Welcome to your new ${whiteLabelText} workspace`, {
|
||||
whiteLabelText,
|
||||
})}
|
||||
</h3>
|
||||
<p className={`empty-title`} data-cy="empty-homepage-description">
|
||||
{t(
|
||||
'blankPage.getStartedCreateNewApp',
|
||||
'You can get started by creating a new application or by creating an application using a template in ToolJet Library.'
|
||||
`You can get started by creating a new application or by creating an application using a template in ${whiteLabelText} Library.`,
|
||||
{
|
||||
whiteLabelText,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<div className="row mt-4">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
const [versionId, setVersionId] = useState(undefined);
|
||||
const [exportTjDb, setExportTjDb] = useState(true);
|
||||
const [currentVersion, setCurrentVersion] = useState(undefined);
|
||||
const [versionSelectLoading, setVersionSelectLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -19,8 +20,11 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
setLoading(true);
|
||||
try {
|
||||
const fetchVersions = await appsService.getVersions(app.id);
|
||||
const fetchTables = await appsService.getTables(app.id); // this is used to get all tables
|
||||
const { versions } = fetchVersions;
|
||||
const { tables } = fetchTables;
|
||||
setVersions(versions);
|
||||
setAllTables(tables);
|
||||
const currentEditingVersion = versions?.filter((version) => version?.isCurrentEditingVersion)[0];
|
||||
if (currentEditingVersion) {
|
||||
setCurrentVersion(currentEditingVersion);
|
||||
|
|
@ -39,11 +43,9 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
|
||||
useEffect(() => {
|
||||
async function fetchAppTables() {
|
||||
setLoading(true);
|
||||
setVersionSelectLoading(true);
|
||||
try {
|
||||
if (!versionId) return;
|
||||
const fetchTables = await appsService.getTables(app.id); // this is used to get all tables
|
||||
const { tables } = fetchTables;
|
||||
const tbl = await appsService.getAppByVersion(app.id, versionId); // this is used to get particular App by version
|
||||
const { dataQueries } = tbl;
|
||||
const extractedIdData = [];
|
||||
|
|
@ -68,14 +70,13 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
const uniqueSet = new Set(extractedIdData);
|
||||
const selectedVersiontable = Array.from(uniqueSet).map((item) => ({ table_id: item }));
|
||||
setTables(selectedVersiontable);
|
||||
setAllTables(tables);
|
||||
} catch (error) {
|
||||
toast.error('Could not fetch the tables.', {
|
||||
position: 'top-center',
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
setLoading(false);
|
||||
setVersionSelectLoading(false);
|
||||
}
|
||||
fetchAppTables();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -209,7 +210,7 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
Export All
|
||||
</ButtonSolid>
|
||||
<ButtonSolid
|
||||
className="import-export-footer-btns"
|
||||
className={`import-export-footer-btns ${versionSelectLoading ? 'btn-loading' : ''}`}
|
||||
data-cy="export-selected-version-button"
|
||||
onClick={() => exportApp(app, versionId, exportTjDb, tables)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ export const Folders = function Folders({
|
|||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col d-flex modal-footer-btn">
|
||||
<div className="col d-flex modal-footer-btn justify-content-end">
|
||||
<ButtonSolid variant="tertiary" onClick={closeModal} data-cy="cancel-button">
|
||||
{t('globals.cancel', 'Cancel')}
|
||||
</ButtonSolid>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ import Footer from './Footer';
|
|||
import { OrganizationList } from '@/_components/OrganizationManager/List';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import BulkIcon from '@/_ui/Icon/bulkIcons/index';
|
||||
import { getWorkspaceId, pageTitles, setWindowTitle } from '@/_helpers/utils';
|
||||
import { getWorkspaceId } from '@/_helpers/utils';
|
||||
import { getQueryParams } from '@/_helpers/routes';
|
||||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import FolderFilter from './FolderFilter';
|
||||
import { APP_ERROR_TYPE } from '@/_helpers/error_constants';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { fetchAndSetWindowTitle, pageTitles } from '@white-label/whiteLabelling';
|
||||
|
||||
const { iconList, defaultIcon } = configs;
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ class HomePageComponent extends React.Component {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
setWindowTitle({ page: pageTitles.DASHBOARD });
|
||||
fetchAndSetWindowTitle({ page: pageTitles.DASHBOARD });
|
||||
this.fetchApps(1, this.state.currentFolder.id);
|
||||
this.fetchFolders();
|
||||
this.setQueryParameter();
|
||||
|
|
@ -238,7 +239,13 @@ class HomePageComponent extends React.Component {
|
|||
};
|
||||
event.target.value = null;
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
let errorMessage = 'Some Error Occured';
|
||||
if (error?.error) {
|
||||
errorMessage = error.error;
|
||||
} else if (error?.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -271,7 +278,7 @@ class HomePageComponent extends React.Component {
|
|||
if (error.statusCode === 409) {
|
||||
return false;
|
||||
}
|
||||
toast.error(error?.error || 'App import failed');
|
||||
toast.error(error?.error || error?.message || 'App import failed');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -720,7 +727,7 @@ class HomePageComponent extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col d-flex modal-footer-btn">
|
||||
<div className="col d-flex modal-footer-btn justify-content-end">
|
||||
<ButtonSolid
|
||||
variant="tertiary"
|
||||
onClick={() => this.setState({ showAddToFolderModal: false, appOperations: {} })}
|
||||
|
|
@ -750,7 +757,7 @@ class HomePageComponent extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col d-flex modal-footer-btn">
|
||||
<div className="col d-flex modal-footer-btn justify-content-end">
|
||||
<ButtonSolid
|
||||
onClick={() => this.setState({ showChangeIconModal: false, appOperations: {} })}
|
||||
data-cy="cancel-button"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { onLoginSuccess } from '@/_helpers/platform/utils/auth.utils';
|
|||
import { updateCurrentSession } from '@/_helpers/authorizeWorkspace';
|
||||
import cx from 'classnames';
|
||||
import SSOLoginModule from './SSOLoginModule';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
class LoginPageComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -31,6 +32,7 @@ class LoginPageComponent extends React.Component {
|
|||
this.paramOrganizationSlug = props?.params?.organizationId;
|
||||
}
|
||||
darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
componentDidMount() {
|
||||
/* remove login oranization's id and slug from the cookie */
|
||||
|
|
@ -130,7 +132,9 @@ class LoginPageComponent extends React.Component {
|
|||
const signUpCTA = workspaceSignUpEnabled ? 'Sign up' : 'Create an account';
|
||||
const signupText = workspaceSignUpEnabled
|
||||
? this.props.t('loginSignupPage.newToWorkspace', `New to this workspace?`)
|
||||
: this.props.t('loginSignupPage.newToTooljet', `New to Tooljet?`);
|
||||
: this.props.t('loginSignupPage.newToTooljet', ` New to ${this.whiteLabelText}?`, {
|
||||
whiteLabelText: this.whiteLabelText,
|
||||
});
|
||||
const signUpUrl = `/signup${this.paramOrganizationSlug ? `/${this.paramOrganizationSlug}` : ''}${
|
||||
redirectTo ? `?redirectTo=${redirectTo}` : ''
|
||||
}`;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import ManageOrgVarsDrawer from './ManageOrgVarsDrawer';
|
|||
import { Alert } from '@/_ui/Alert/Alert';
|
||||
import { Button } from '@/_ui/LeftSidebar';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
function useWorkspaceRouting() {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -89,7 +90,7 @@ class RawManageOrgVarsComponent extends React.Component {
|
|||
});
|
||||
|
||||
orgEnvironmentVariableService.getVariables().then((data) => {
|
||||
const variables = _.cloneDeep(data.variables)?.filter(({ variable_name }) => !/copilot_/.test(variable_name));
|
||||
const variables = deepClone(data.variables)?.filter(({ variable_name }) => !/copilot_/.test(variable_name));
|
||||
this.setState({
|
||||
variables: variables,
|
||||
isLoading: false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import useRouter from '@/_hooks/use-router';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { appService, authenticationService } from '@/_services';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import Configs from './Configs/Config.json';
|
||||
import { RedirectLoader } from '../_components';
|
||||
|
|
@ -85,7 +85,22 @@ export function Authorize({ navigate }) {
|
|||
const inviteeEmail = details?.inviteeEmail;
|
||||
if (inviteeEmail) setInviteeEmail(inviteeEmail);
|
||||
const errMessage = details?.message || err?.error || 'something went wrong';
|
||||
setError(`${configs.name} login failed - ${errMessage}`);
|
||||
if (!inviteeEmail && inviteFlowIdentifier) {
|
||||
/* Some unexpected error happened from the provider side. Need to retreive email to continue */
|
||||
appService
|
||||
.getInviteeDetails(inviteFlowIdentifier)
|
||||
.then((response) => {
|
||||
setInviteeEmail(response.email);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('Error while fetching invitee details');
|
||||
})
|
||||
.finally(() => {
|
||||
setError(`${configs.name} login failed - ${errMessage}`);
|
||||
});
|
||||
} else {
|
||||
setError(`${configs.name} login failed - ${errMessage}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import OnBoardingRadioInput from './OnBoardingRadioInput';
|
|||
import ContinueButton from './ContinueButton';
|
||||
import OnBoardingBubbles from './OnBoardingBubbles';
|
||||
import { getuserName } from '@/_helpers/utils';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
import { redirectToDashboard } from '@/_helpers/routes';
|
||||
import { ON_BOARDING_SIZE, ON_BOARDING_ROLES } from '@/_helpers/constants';
|
||||
import LogoLightMode from '@assets/images/Logomark.svg';
|
||||
|
|
@ -26,6 +27,7 @@ function OnBoardingForm({ userDetails = {}, token = '', organizationToken = '',
|
|||
phoneNumber: '',
|
||||
});
|
||||
const source = new URLSearchParams(location?.search).get('source');
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
const pageProps = {
|
||||
formData,
|
||||
|
|
@ -75,7 +77,7 @@ function OnBoardingForm({ userDetails = {}, token = '', organizationToken = '',
|
|||
'Enter your phone number',
|
||||
'Enter your phone number', //dummy for styling
|
||||
];
|
||||
const FormSubTitles = ['This information will help us improve ToolJet.'];
|
||||
const FormSubTitles = [`This information will help us improve ${whiteLabelText}.`];
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ import { useSessionManagement } from '@/_hooks/useSessionManagement';
|
|||
import { getRedirectURL, pathnameToArray } from '@/_helpers/routes';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import {
|
||||
resetToDefaultWhiteLabels,
|
||||
retrieveWhiteLabelFavicon,
|
||||
retrieveWhiteLabelText,
|
||||
setFaviconAndTitle,
|
||||
} from '@white-label/whiteLabelling';
|
||||
|
||||
export const AuthRoute = ({ children, navigate }) => {
|
||||
const { isLoading, session, isValidSession, isInvalidSession, setLoading } = useSessionManagement({
|
||||
|
|
@ -29,8 +35,7 @@ export const AuthRoute = ({ children, navigate }) => {
|
|||
|
||||
useEffect(
|
||||
() => {
|
||||
authenticationService.deleteAllAuthCookies();
|
||||
fetchOrganizationDetails();
|
||||
initialize();
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[location.pathname]
|
||||
|
|
@ -43,11 +48,30 @@ export const AuthRoute = ({ children, navigate }) => {
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
const isComingFromPasswordReset = location?.state?.from === '/reset-password';
|
||||
const isComingFromPasswordReset =
|
||||
location?.state?.from === '/reset-password' || location?.state?.from === '/forgot-password';
|
||||
if ((isInvalidSession || isComingFromPasswordReset) && !isGettingConfigs) setLoading(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isInvalidSession, isGettingConfigs]);
|
||||
|
||||
const initialize = () => {
|
||||
const pathname = location.pathname;
|
||||
authenticationService.deleteAllAuthCookies();
|
||||
fetchOrganizationDetails();
|
||||
verifyWhiteLabeling(pathname);
|
||||
};
|
||||
|
||||
const verifyWhiteLabeling = (pathname) => {
|
||||
const signupRegex = /^\/signup\/[^/]+$/;
|
||||
const loginRegex = /^\/login\/[^/]+$/;
|
||||
if (!signupRegex.test(pathname) && !loginRegex.test(pathname)) {
|
||||
resetToDefaultWhiteLabels();
|
||||
}
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
const whiteLabelFavicon = retrieveWhiteLabelFavicon();
|
||||
setFaviconAndTitle(whiteLabelFavicon, whiteLabelText, location);
|
||||
};
|
||||
|
||||
const fetchOrganizationDetails = () => {
|
||||
authenticationService.getOrganizationConfigs(organizationSlug).then(
|
||||
(configs) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { RouteLoader } from './RouteLoader';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { appService, authenticationService } from '@/_services';
|
||||
import { authorizeUserAndHandleErrors, updateCurrentSession } from '@/_helpers/authorizeWorkspace';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { LinkExpiredPage } from '@/ConfirmationPage/LinkExpiredPage';
|
||||
import { onLoginSuccess } from '@/_helpers/platform/utils/auth.utils';
|
||||
|
||||
export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite, navigate }) => {
|
||||
/* Needed to pass invite token to signup page if the user doesn't exist */
|
||||
|
|
@ -15,6 +16,8 @@ export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite,
|
|||
const organizationToken = params.organizationToken || (isOrgazanizationOnlyInvite ? params.token : null);
|
||||
const accountToken = !isOrgazanizationOnlyInvite ? params.token : null;
|
||||
const [extraProps, setExtraProps] = useState({});
|
||||
const searchParams = new URLSearchParams(location?.search);
|
||||
const redirectTo = searchParams.get('redirectTo');
|
||||
|
||||
useEffect(() => {
|
||||
getInvitedUserSession();
|
||||
|
|
@ -33,6 +36,8 @@ export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite,
|
|||
name,
|
||||
organization_invite_url,
|
||||
is_workspace_sign_up_invite,
|
||||
source,
|
||||
organization_user_source,
|
||||
} = invitedUserSession;
|
||||
/*
|
||||
We should only run the authorization against the session if the user has active workspace
|
||||
|
|
@ -42,6 +47,10 @@ export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite,
|
|||
email,
|
||||
name,
|
||||
});
|
||||
if (source === 'workspace_signup' || organization_user_source === 'signup') {
|
||||
acceptInvite(accountToken, organizationToken, navigate, source, redirectTo);
|
||||
return;
|
||||
}
|
||||
if (is_workspace_sign_up_invite) {
|
||||
setLoading(false);
|
||||
return;
|
||||
|
|
@ -141,6 +150,41 @@ export const OrganizationInviteRoute = ({ children, isOrgazanizationOnlyInvite,
|
|||
);
|
||||
};
|
||||
|
||||
const acceptInvite = (token, organizationToken, navigate, source, redirectTo) => {
|
||||
if (token && organizationToken) {
|
||||
authenticationService
|
||||
.onboarding({
|
||||
token,
|
||||
organizationToken,
|
||||
source,
|
||||
})
|
||||
.then((user) => {
|
||||
onLoginSuccess(user, navigate, redirectTo);
|
||||
})
|
||||
.catch((res) => {
|
||||
toast.error(res.error || 'Something went wrong', {
|
||||
id: 'toast-login-auth-error',
|
||||
position: 'top-center',
|
||||
});
|
||||
});
|
||||
} else {
|
||||
appService
|
||||
.acceptInvite({
|
||||
token: organizationToken,
|
||||
})
|
||||
.then((data) => {
|
||||
toast.success(`Added to the workspace successfully.`);
|
||||
updateCurrentSession({
|
||||
isUserLoggingIn: true,
|
||||
});
|
||||
onLoginSuccess(data, navigate);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Error while setting up your account.', { position: 'top-center' });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (invalidLink) return <LinkExpiredPage />;
|
||||
|
||||
const clonedElement = React.cloneElement(children || <></>, extraProps);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { extractErrorObj, onInvitedUserSignUpSuccess } from '@/_helpers/platform
|
|||
import { isEmpty } from 'lodash';
|
||||
import { EmailComponent } from './EmailComponent';
|
||||
import SSOLoginModule from '@/LoginPage/SSOLoginModule';
|
||||
import { checkWhiteLabelsDefaultState } from '@white-label/whiteLabelling';
|
||||
class SignupPageComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -34,6 +35,7 @@ class SignupPageComponent extends React.Component {
|
|||
emailError: '',
|
||||
disableOnEdit: false,
|
||||
email: this.inviteeEmail || '',
|
||||
defaultState: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +44,9 @@ class SignupPageComponent extends React.Component {
|
|||
if (errorMessage) {
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
checkWhiteLabelsDefaultState(this.inviteOrganizationId).then((res) => {
|
||||
this.setState({ defaultState: res });
|
||||
});
|
||||
}
|
||||
|
||||
backtoSignup = (email, name) => {
|
||||
|
|
@ -122,7 +127,7 @@ class SignupPageComponent extends React.Component {
|
|||
|
||||
render() {
|
||||
const { configs } = this.props;
|
||||
const { isLoading, signupSuccess } = this.state;
|
||||
const { isLoading, signupSuccess, defaultState } = this.state;
|
||||
const comingFromInviteFlow = !!this.organizationToken;
|
||||
const isSignUpButtonDisabled =
|
||||
isLoading ||
|
||||
|
|
@ -320,20 +325,22 @@ class SignupPageComponent extends React.Component {
|
|||
</>
|
||||
)}
|
||||
|
||||
<p className="signup-terms" data-cy="signup-terms-helper">
|
||||
By signing up you are agreeing to the
|
||||
<br />
|
||||
<span>
|
||||
<a href="https://www.tooljet.com/terms" data-cy="terms-of-service-link">
|
||||
Terms of Service{' '}
|
||||
</a>
|
||||
&
|
||||
<a href="https://www.tooljet.com/privacy" data-cy="privacy-policy-link">
|
||||
{' '}
|
||||
Privacy Policy
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
{defaultState && (
|
||||
<p className="signup-terms" data-cy="signup-terms-helper">
|
||||
By signing up you are agreeing to the
|
||||
<br />
|
||||
<span>
|
||||
<a href="https://www.tooljet.com/terms" data-cy="terms-of-service-link">
|
||||
Terms of Service{' '}
|
||||
</a>
|
||||
&
|
||||
<a href="https://www.tooljet.com/privacy" data-cy="privacy-policy-link">
|
||||
{' '}
|
||||
Privacy Policy
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
@ -347,6 +354,8 @@ class SignupPageComponent extends React.Component {
|
|||
name={this.state.name}
|
||||
backtoSignup={this.backtoSignup}
|
||||
darkMode={this.darkMode}
|
||||
organizationId={this.inviteOrganizationId}
|
||||
redirectTo={this.redirectTo}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import { ButtonSolid } from '@/_components/AppButton';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
|
||||
export const PasswordResetinfoScreen = function PasswordResetinfoScreen({ darkMode }) {
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div className="info-screen-wrapper">
|
||||
|
|
@ -22,7 +24,7 @@ export const PasswordResetinfoScreen = function PasswordResetinfoScreen({ darkMo
|
|||
Password has been reset
|
||||
</h1>
|
||||
<p className="info-screen-description" data-cy="reset-password-page-description">
|
||||
Your password has been reset successfully, log into ToolJet to continue your session
|
||||
Your password has been reset successfully, log into {whiteLabelText} to continue your session
|
||||
</p>
|
||||
<ButtonSolid
|
||||
variant="secondary"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,14 @@ import { authenticationService } from '@/_services';
|
|||
import { toast } from 'react-hot-toast';
|
||||
import Spinner from '@/_ui/Spinner';
|
||||
|
||||
export const SignupInfoScreen = function SignupInfoScreen({ email, backtoSignup, name, darkMode }) {
|
||||
export const SignupInfoScreen = function SignupInfoScreen({
|
||||
email,
|
||||
backtoSignup,
|
||||
name,
|
||||
darkMode,
|
||||
organizationId,
|
||||
redirectTo,
|
||||
}) {
|
||||
const [resendBtn, setResetBtn] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [buttonText, setButtonText] = useState('Resend verification mail in 30s');
|
||||
|
|
@ -30,7 +37,7 @@ export const SignupInfoScreen = function SignupInfoScreen({ email, backtoSignup,
|
|||
|
||||
e.preventDefault();
|
||||
authenticationService
|
||||
.resendInvite(email)
|
||||
.resendInvite(email, organizationId, redirectTo)
|
||||
.then(() => {
|
||||
setIsLoading(false);
|
||||
setResetBtn(true);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import EnterIcon from '../../assets/images/onboardingassets/Icons/Enter';
|
||||
import GoogleSSOLoginButton from '@ee/components/LoginPage/GoogleSSOLoginButton';
|
||||
import GitSSOLoginButton from '@ee/components/LoginPage/GitSSOLoginButton';
|
||||
import OnBoardingForm from '../OnBoardingForm/OnBoardingForm';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { LinkExpiredInfoScreen } from '@/SuccessInfoScreen';
|
||||
import { ShowLoading } from '@/_components';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
|
@ -15,7 +13,9 @@ import EyeShow from '../../assets/images/onboardingassets/Icons/EyeShow';
|
|||
import Spinner from '@/_ui/Spinner';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { buildURLWithQuery } from '@/_helpers/utils';
|
||||
import { onLoginSuccess } from '@/_helpers/platform/utils/auth.utils';
|
||||
import { redirectToDashboard } from '@/_helpers/routes';
|
||||
import { retrieveWhiteLabelText, setFaviconAndTitle, checkWhiteLabelsDefaultState } from '@white-label/whiteLabelling';
|
||||
|
||||
export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScreen() {
|
||||
const [showOnboarding, setShowOnboarding] = useState(false);
|
||||
|
|
@ -29,6 +29,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [fallBack, setFallBack] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [defaultState, setDefaultState] = useState(false);
|
||||
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
|
|
@ -39,6 +40,8 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
const source = searchParams.get('source');
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
const redirectTo = searchParams.get('redirectTo');
|
||||
const navigate = useNavigate();
|
||||
const whiteLabelText = retrieveWhiteLabelText();
|
||||
|
||||
const getUserDetails = () => {
|
||||
setIsLoading(true);
|
||||
|
|
@ -81,6 +84,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
(configs) => {
|
||||
setIsGettingConfigs(false);
|
||||
setConfigs(configs);
|
||||
setFaviconAndTitle(null, null, location);
|
||||
},
|
||||
() => {
|
||||
setIsGettingConfigs(false);
|
||||
|
|
@ -89,6 +93,9 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
} else {
|
||||
setIsGettingConfigs(false);
|
||||
}
|
||||
checkWhiteLabelsDefaultState(organizationId).then((res) => {
|
||||
setDefaultState(res);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
|
@ -121,7 +128,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
.then((user) => {
|
||||
authenticationService.deleteLoginOrganizationId();
|
||||
setIsLoading(false);
|
||||
redirectToDashboard(user, redirectTo);
|
||||
onLoginSuccess(user, navigate, redirectTo);
|
||||
})
|
||||
.catch((res) => {
|
||||
setIsLoading(false);
|
||||
|
|
@ -180,14 +187,14 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
) : (
|
||||
<div className="common-auth-container-wrapper">
|
||||
<h2 className="common-auth-section-header org-invite-header" data-cy="invite-page-header">
|
||||
Join {configs?.name ? configs?.name : 'ToolJet'}
|
||||
Join {configs?.name ? configs?.name : whiteLabelText}
|
||||
</h2>
|
||||
|
||||
<div className="invite-sub-header" data-cy="invite-page-sub-header">
|
||||
{`You are invited to ${
|
||||
configs?.name
|
||||
? `a workspace ${configs?.name}. Accept the invite to join the workspace.`
|
||||
: 'ToolJet.'
|
||||
: `${whiteLabelText}.`
|
||||
}`}
|
||||
</div>
|
||||
|
||||
|
|
@ -288,20 +295,22 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
)}
|
||||
</ButtonSolid>
|
||||
</div>
|
||||
<p className="verification-terms" data-cy="signup-terms-helper">
|
||||
By signing up you are agreeing to the
|
||||
<br />
|
||||
<span>
|
||||
<a href="https://www.tooljet.com/terms" data-cy="terms-of-service-link">
|
||||
Terms of Service{' '}
|
||||
</a>
|
||||
&
|
||||
<a href="https://www.tooljet.com/privacy" data-cy="privacy-policy-link">
|
||||
{' '}
|
||||
Privacy Policy
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
{defaultState && (
|
||||
<p className="verification-terms" data-cy="signup-terms-helper">
|
||||
By signing up you are agreeing to the
|
||||
<br />
|
||||
<span>
|
||||
<a href="https://www.tooljet.com/terms" data-cy="terms-of-service-link">
|
||||
Terms of Service{' '}
|
||||
</a>
|
||||
&
|
||||
<a href="https://www.tooljet.com/privacy" data-cy="privacy-policy-link">
|
||||
{' '}
|
||||
Privacy Policy
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
|
|
@ -331,7 +340,7 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
{t('verificationSuccessPage.successfullyVerifiedEmail', 'Successfully verified email')}
|
||||
</h1>
|
||||
<p className="info-screen-description" data-cy="onboarding-page-description">
|
||||
Continue to set up your workspace to start using ToolJet.
|
||||
Continue to set up your workspace to start using {whiteLabelText}.
|
||||
</p>
|
||||
<ButtonSolid
|
||||
className="verification-success-info-btn "
|
||||
|
|
@ -347,7 +356,10 @@ export const VerificationSuccessInfoScreen = function VerificationSuccessInfoScr
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
{t('verificationSuccessPage.setupTooljet', 'Set up ToolJet')}
|
||||
{t('verificationSuccessPage.setupTooljet', `Set up ${whiteLabelText}`, {
|
||||
whiteLabelText,
|
||||
})}
|
||||
|
||||
<EnterIcon fill={'#fff'}></EnterIcon>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
import React from 'react';
|
||||
import List from './List';
|
||||
import ListGroup from 'react-bootstrap/ListGroup';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'List',
|
||||
component: List,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
||||
// tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
};
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
export const WithIcon = {
|
||||
render: (args) => (
|
||||
<List>
|
||||
<List.Item>Client side</List.Item>
|
||||
<List.Item active>Server side</List.Item>
|
||||
<List.Item>Client side</List.Item>
|
||||
<List.Item disabled>Server side</List.Item>
|
||||
<List.Item>Client side</List.Item>
|
||||
<List.Item>Server side</List.Item>
|
||||
<List.Item>Client side</List.Item>
|
||||
<List.Item>Server side</List.Item>
|
||||
</List>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithIconAndEdit = {
|
||||
render: (args) => (
|
||||
<ListGroup>
|
||||
<ListGroup.Item>Cras justo odio</ListGroup.Item>
|
||||
<ListGroup.Item>Dapibus ac facilisis in</ListGroup.Item>
|
||||
<ListGroup.Item>Morbi leo risus</ListGroup.Item>
|
||||
<ListGroup.Item>Porta ac consectetur ac</ListGroup.Item>
|
||||
<ListGroup.Item>Vestibulum at eros</ListGroup.Item>
|
||||
</ListGroup>
|
||||
),
|
||||
};
|
||||
|
|
@ -5,7 +5,7 @@ import ToggleGroupItem from './ToggleGroupItem';
|
|||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'ToggleGroup',
|
||||
title: 'Components/ToggleGroup',
|
||||
component: ToggleGroup,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import { Tabs } from './Tabs';
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
title: 'Tabs',
|
||||
component: Tabs,
|
||||
parameters: {
|
||||
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||
layout: 'centered',
|
||||
},
|
||||
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
||||
tags: ['autodocs'],
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
};
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
export const Primary = {
|
||||
args: {
|
||||
primary: true,
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary = {
|
||||
args: {
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Large = {
|
||||
args: {
|
||||
size: 'large',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
||||
export const Small = {
|
||||
args: {
|
||||
size: 'small',
|
||||
label: 'Button',
|
||||
},
|
||||
};
|
||||
|
|
@ -11,6 +11,7 @@ import WarningInfo from '../Icons/Edit-information.svg';
|
|||
import { ConfirmDialog } from '@/_components';
|
||||
import { serialDataType } from '../constants';
|
||||
import cx from 'classnames';
|
||||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
const TableForm = ({
|
||||
selectedTable = {},
|
||||
|
|
@ -38,7 +39,7 @@ const TableForm = ({
|
|||
const [showModal, setShowModal] = useState(false);
|
||||
const [createForeignKeyInEdit, setCreateForeignKeyInEdit] = useState(false);
|
||||
const [tableName, setTableName] = useState(selectedTable.table_name);
|
||||
const [columns, setColumns] = useState(_.cloneDeep(selectedTableColumns));
|
||||
const [columns, setColumns] = useState(deepClone(selectedTableColumns));
|
||||
const { organizationId, foreignKeys, setForeignKeys } = useContext(TooljetDatabaseContext);
|
||||
const { updateSidebarNAV } = useContext(BreadCrumbContext);
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue