Merge branch 'develop' into feature/canvas-grid-resizer

This commit is contained in:
arpitnath 2021-09-23 11:23:27 +05:30
commit eccd8a7cf2
282 changed files with 9077 additions and 6207 deletions

View file

@ -1,2 +0,0 @@
frontend/node_modules/**
# **/*.js

View file

@ -1,8 +0,0 @@
{
"semi": true,
"trailingComma": "es5",
"printWidth": 120,
"singleQuote": true,
"arrowParens": "always",
"proseWrap": "preserve"
}

View file

@ -1 +1 @@
0.7.2
0.7.3

11
.vscode/extension.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"CoenraadS.bracket-pair-colorizer",
"mgmcdermott.vscode-language-babel",
"formulahendry.auto-rename-tag",
"xabikos.javascriptsnippets",
"streetsidesoftware.code-spell-checker",
"esbenp.prettier-vscode"
]
}

14
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
"[javascript, typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.format.enable": true,
"editor.formatOnSave": true,
}

View file

@ -6,14 +6,21 @@
ToolJet is an **open-source no-code framework** to build and deploy internal tools quickly without much effort from the engineering teams. You can connect to your data sources such as databases ( like PostgreSQL, MongoDB, Elasticsearch, etc ), API endpoints ( ToolJet supports importing OpenAPI spec & OAuth2 authorization) and external services ( like Stripe, Slack, Google Sheets, Airtable ) and use our pre-built UI widgets to build internal tools.
![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/tooljet/tooljet-ce)
![GitHub contributors](https://img.shields.io/github/contributors/tooljet/tooljet)
[![GitHub issues](https://img.shields.io/github/issues/ToolJet/ToolJet)](https://github.com/ToolJet/ToolJet/issues)
[![GitHub stars](https://img.shields.io/github/stars/ToolJet/ToolJet)](https://github.com/ToolJet/ToolJet/stargazers)
![GitHub closed issues](https://img.shields.io/github/issues-closed/tooljet/tooljet)
![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/tooljet/tooljet)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/tooljet/tooljet)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/tooljet/tooljet)
[![GitHub license](https://img.shields.io/github/license/ToolJet/ToolJet)](https://github.com/ToolJet/ToolJet)
<p align="center">
<kbd>
<img src="https://user-images.githubusercontent.com/7828962/130593222-d1268766-b776-4952-bd36-9caf9810d851.gif" />
<img src="https://user-images.githubusercontent.com/7828962/134216201-b2c65c48-547a-4e79-946c-60b7be54b70c.png" />
</kbd>
</p>
@ -29,7 +36,6 @@ ToolJet is an **open-source no-code framework** to build and deploy internal too
- Write JS code almost anywhere in the builder
- Query editors for all supported data sources
- Transform query results using JS code
- Import endpoints from OpenAPI specs
- All the credentials are securely encrypted using `aes-256-gcm`.
- ToolJet acts only as a proxy and doesn't store any data.
- Support for OAuth
@ -44,8 +50,6 @@ You can deploy ToolJet on Heroku for free using the one-click-deployment button
<a href="https://heroku.com/deploy?template=https://github.com/tooljet/tooljet/tree/main"><img src="https://www.herokucdn.com/deploy/button.svg" /></a>
</P>
## Examples
[Building a Github contributor leaderboard using ToolJet](https://blog.tooljet.io/building-a-github-contributor-leaderboard-using-tooljet/)<br>
@ -65,5 +69,10 @@ We use the git-flow branching model. The base branch is develop. If you are look
Read our contributing guide (CONTRIBUTING.md) to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to ToolJet. <br>
[Contributing Guide](https://docs.tooljet.io/docs/contributing-guide/setup/docker)
## Contributors
<a href="https://github.com/tooljet/tooljet/graphs/contributors">
<img src="https://contrib.rocks/image?repo=tooljet/tooljet" />
</a>
## Licence
ToolJet © 2021, ToolJet Inc - Released under the GNU General Public License v3.0.
ToolJet © 2021, ToolJet Solutions Inc - Released under the GNU General Public License v3.0.

View file

@ -12,6 +12,7 @@ ENV PATH /app/node_modules/.bin:$PATH
# Fix for heap limit allocation issue
ENV NODE_OPTIONS="--max-old-space-size=2048"
# install app dependencies
COPY package.json package-lock.json ./
RUN npm install

View file

@ -1,37 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true
},
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 12,
sourceType: 'module'
},
extends: [
'plugin:react/recommended',
'prettier',
'airbnb-base/legacy'
],
plugins: ['html', 'react', 'prettier', 'babel'],
rules: {
"react/prop-types": 0,
"no-underscore-dangle": ["error", { "allow": ["_self"] }],
"max-len": 0,
"no-bitwise": 0,
"no-use-before-define": ["error", { "variables": false, "functions": false }],
"no-nested-ternary": 0,
"no-loop-func": 0,
},
settings: {
react: {
version: "detect"
}
}
};

56
frontend/.eslintrc.json Normal file
View file

@ -0,0 +1,56 @@
{
"env": {
"browser": true,
"amd": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:prettier/recommended",
"plugin:cypress/recommended"
],
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["react", "prettier"],
"rules": {
"prettier/prettier": [
"error",
{
"semi": true,
"trailingComma": "es5",
"printWidth": 120,
"singleQuote": true,
"arrowParens": "always",
"proseWrap": "preserve"
}
],
"react/prop-types": 0,
"react/display-name": "off",
"no-unused-vars": [2, { "args": "after-used", "argsIgnorePattern": "reject" }],
"react/no-deprecated": 0,
"no-prototype-builtins": 0
},
"settings": {
"react": {
"version": "detect"
},
"import/resolver": "webpack"
},
"globals": {
"fetch": true,
"process": true,
"module": true,
"__dirname": true
}
}

View file

@ -1,16 +1,12 @@
const webpackPreprocessor = require('@cypress/webpack-preprocessor')
module.exports = (on, config) => {
if (config.testingType === 'component') {
const { startDevServer } = require('@cypress/webpack-dev-server')
const { startDevServer } = require('@cypress/webpack-dev-server');
// Your project's Webpack configuration
const webpackConfig = require('../../webpack.config.js')
const webpackConfig = require('../../webpack.config.js');
on('dev-server:start', (options) =>
startDevServer({ options, webpackConfig })
)
on('dev-server:start', (options) => startDevServer({ options, webpackConfig }));
}
return config
}
return config;
};

View file

@ -14,7 +14,7 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,6 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"babel-plugin-import": "^1.13.3",
"@babel/core": "^7.4.3",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.4.3",
@ -18,6 +17,7 @@
"array-move": "^3.0.1",
"babel-loader": "^8.0.5",
"babel-plugin-console-source": "^2.0.5",
"babel-plugin-import": "^1.13.3",
"bootstrap": "^4.6.0",
"dompurify": "^2.2.7",
"draft-js": "^0.11.7",
@ -32,7 +32,6 @@
"papaparse": "^5.3.0",
"plotly.js-basic-dist-min": "^1.58.4",
"query-string": "^6.13.6",
"re-resizable": "^6.9.0",
"react": "^16.14.0",
"react-bootstrap": "^1.5.2",
"react-color": "^2.19.3",
@ -48,7 +47,6 @@
"react-loading-skeleton": "^2.2.0",
"react-plotly.js": "^2.5.1",
"react-qr-reader": "^2.2.1",
"react-resizable": "^1.11.1",
"react-rnd": "^10.3.0",
"react-router-dom": "^5.0.0",
"react-scripts": "3.4.3",
@ -71,13 +69,24 @@
"@cypress/webpack-preprocessor": "^5.9.0",
"@svgr/webpack": "^5.5.0",
"cypress": "^7.4.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.1",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-react": "^7.25.2",
"eslint-plugin-react-hooks": "^4.2.0",
"path": "^0.12.7",
"prettier": "^2.3.2",
"webpack": "^4.29.6",
"webpack-dev-server": "^3.11.2"
},
"scripts": {
"start": "webpack-dev-server --open --port 8082 --host 0.0.0.0",
"build": "webpack -p && cp -a ./assets/. ./build/assets/",
"lint": "eslint . '**/*.{js,jsx}'",
"format": "eslint . --fix '**/*.{js,jsx}'",
"test": "react-scripts test",
"eject": "react-scripts eject"
},

View file

@ -15,7 +15,7 @@ import 'react-toastify/dist/ReactToastify.css';
import { ManageOrgUsers } from '@/ManageOrgUsers';
import { SettingsPage } from '../SettingsPage/SettingsPage';
import { OnboardingModal } from '@/Onboarding/OnboardingModal';
import {ForgotPassword} from '@/ForgotPassword'
import { ForgotPassword } from '@/ForgotPassword';
import { ResetPassword } from '@/ResetPassword';
import { lt } from 'semver';
@ -27,7 +27,7 @@ class App extends React.Component {
currentUser: null,
fetchedMetadata: false,
onboarded: true,
darkMode: localStorage.getItem('darkMode') === 'true'
darkMode: localStorage.getItem('darkMode') === 'true',
};
}
@ -40,56 +40,107 @@ class App extends React.Component {
logout = () => {
authenticationService.logout();
history.push('/login');
}
};
switchDarkMode = (newMode) => {
this.setState({ darkMode: newMode });
localStorage.setItem('darkMode', newMode);
}
};
render() {
const { currentUser, fetchedMetadata, updateAvailable, onboarded, darkMode } = this.state;
if(currentUser && fetchedMetadata === false) {
if (currentUser && fetchedMetadata === false) {
tooljetService.fetchMetaData().then((data) => {
this.setState({ fetchedMetadata: true, onboarded: data.onboarded });
if(lt(data.installed_version, data.latest_version) && data.version_ignored === false) {
if (lt(data.installed_version, data.latest_version) && data.version_ignored === false) {
this.setState({ updateAvailable: true });
}
})
});
}
return (
<Router history={history}>
<div className={`main-wrapper ${darkMode ? 'theme-dark' : ''}`}>
{updateAvailable && <div className="alert alert-info alert-dismissible" role="alert">
<h3 className="mb-1">Update available</h3>
<p>A new version of ToolJet has been released.</p>
<div className="btn-list">
<a href="https://docs.tooljet.io/docs/setup/updating" target="_blank" className="btn btn-info">Read release notes & update</a>
<a onClick={() => { tooljetService.skipVersion(); this.setState({ updateAvailable: false }); }} className="btn">Skip this version</a>
{updateAvailable && (
<div className="alert alert-info alert-dismissible" role="alert">
<h3 className="mb-1">Update available</h3>
<p>A new version of ToolJet has been released.</p>
<div className="btn-list">
<a
href="https://docs.tooljet.io/docs/setup/updating"
target="_blank"
className="btn btn-info"
rel="noreferrer"
>
Read release notes & update
</a>
<a
onClick={() => {
tooljetService.skipVersion();
this.setState({ updateAvailable: false });
}}
className="btn"
>
Skip this version
</a>
</div>
</div>
</div>}
)}
{!onboarded &&
<OnboardingModal />
}
{!onboarded && <OnboardingModal />}
<ToastContainer />
<PrivateRoute exact path="/" component={HomePage} switchDarkMode={this.switchDarkMode} darkMode={darkMode}/>
<Route path="/login" component={LoginPage}/>
<PrivateRoute exact path="/" component={HomePage} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
<Route path="/login" component={LoginPage} />
<Route path="/signup" component={SignupPage} />
<Route path = "/forgot-password" component ={ForgotPassword} />
<Route path = "/reset-password" component ={ResetPassword} />
<Route path="/forgot-password" component={ForgotPassword} />
<Route path="/reset-password" component={ResetPassword} />
<Route path="/invitations/:token" component={InvitationPage} />
<PrivateRoute exact path="/apps/:id" component={Editor} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
<PrivateRoute exact path="/applications/:id/versions/:versionId" component={Viewer} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
<PrivateRoute exact path="/applications/:slug" component={Viewer} switchDarkMode={this.switchDarkMode} darkMode={darkMode}/>
<PrivateRoute exact path="/oauth2/authorize" component={Authorize} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
<PrivateRoute exact path="/users" component={ManageOrgUsers} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
<PrivateRoute exact path="/settings" component={SettingsPage} switchDarkMode={this.switchDarkMode} darkMode={darkMode} />
<PrivateRoute
exact
path="/apps/:id"
component={Editor}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
/>
<PrivateRoute
exact
path="/applications/:id/versions/:versionId"
component={Viewer}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
/>
<PrivateRoute
exact
path="/applications/:slug"
component={Viewer}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
/>
<PrivateRoute
exact
path="/oauth2/authorize"
component={Authorize}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
/>
<PrivateRoute
exact
path="/users"
component={ManageOrgUsers}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
/>
<PrivateRoute
exact
path="/settings"
component={SettingsPage}
switchDarkMode={this.switchDarkMode}
darkMode={darkMode}
/>
</div>
</Router>
);

View file

@ -2,51 +2,39 @@ export const ActionTypes = [
{
name: 'Show Alert',
id: 'show-alert',
options: [
{ name: 'message', type: 'text', default: 'Message !' }
]
options: [{ name: 'message', type: 'text', default: 'Message !' }],
},
{
name: 'Run Query',
id: 'run-query',
options: [
{ queryId: '' }
]
options: [{ queryId: '' }],
},
{
name: 'Open Webpage',
id: 'open-webpage',
options: [
{ name: 'url', type: 'text', default: 'https://example.com' }
]
options: [{ name: 'url', type: 'text', default: 'https://example.com' }],
},
{
name: 'Go to app',
id: 'go-to-app',
options: [
{ name: 'app', type: 'text', default: '' },
{ name: 'queryParams', type: 'code', default: '[]' }
]
{ name: 'queryParams', type: 'code', default: '[]' },
],
},
{
name: 'Show Modal',
id: 'show-modal',
options: [
{ name: 'modal', type: 'text', default: '' }
]
options: [{ name: 'modal', type: 'text', default: '' }],
},
{
name: 'Close Modal',
id: 'close-modal',
options: [
{ name: 'modal', type: 'text', default: '' }
]
options: [{ name: 'modal', type: 'text', default: '' }],
},
{
name: 'Copy to clipboard',
id: 'copy-to-clipboard',
options: [
{ name: 'copy-to-clipboard', type: 'text', default: '' }
]
}
options: [{ name: 'copy-to-clipboard', type: 'text', default: '' }],
},
];

View file

@ -17,9 +17,9 @@ import { Modal } from './Components/Modal';
import { Chart } from './Components/Chart';
import { Map } from './Components/Map/Map';
import { QrScanner } from './Components/QrScanner/QrScanner';
import { ToggleSwitch } from './Components/Toggle'
import { RadioButton } from './Components/RadioButton'
import { StarRating } from './Components/StarRating'
import { ToggleSwitch } from './Components/Toggle';
import { RadioButton } from './Components/RadioButton';
import { StarRating } from './Components/StarRating';
import { renderTooltip } from '../_helpers/appUtils';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import '@/_styles/custom.scss';
@ -45,12 +45,11 @@ const AllComponents = {
QrScanner,
ToggleSwitch,
RadioButton,
StarRating
StarRating,
};
export const Box = function Box({
id,
mode,
width,
height,
yellow,
@ -66,7 +65,7 @@ export const Box = function Box({
changeCanDrag,
containerProps,
darkMode,
removeComponent
removeComponent,
}) {
const backgroundColor = yellow ? 'yellow' : '';
@ -86,49 +85,49 @@ export const Box = function Box({
<OverlayTrigger
placement="top"
delay={{ show: 500, hide: 0 }}
trigger={!inCanvas? ['hover', 'focus']: null}
overlay={(props) => renderTooltip({props, text: `${component.description}`})}
trigger={!inCanvas ? ['hover', 'focus'] : null}
overlay={(props) => renderTooltip({ props, text: `${component.description}` })}
>
<div style={{ ...styles, backgroundColor }} role={preview ? 'BoxPreview' : 'Box'}>
{inCanvas ? (
<ComponentToRender
onComponentClick={onComponentClick}
onComponentOptionChanged={onComponentOptionChanged}
currentState={currentState}
onEvent={onEvent}
id={id}
paramUpdated={paramUpdated}
width={width}
changeCanDrag={changeCanDrag}
onComponentOptionsChanged={onComponentOptionsChanged}
height={height}
component={component}
containerProps={containerProps}
darkMode={darkMode}
removeComponent={removeComponent}
></ComponentToRender>
) : (
<div className="m-1" style={{ height: '100%' }}>
<div
className="component-image-holder p-2 d-flex flex-column justify-content-center"
style={{ height: '100%' }}
>
<center>
<div
style={{
width: '20px',
height: '20px',
backgroundSize: 'contain',
backgroundImage: `url(/assets/images/icons/widgets/${component.name.toLowerCase()}.svg)`,
backgroundRepeat: 'no-repeat',
}}
></div>
</center>
<span className="component-title">{component.displayName}</span>
<div style={{ ...styles, backgroundColor }} role={preview ? 'BoxPreview' : 'Box'}>
{inCanvas ? (
<ComponentToRender
onComponentClick={onComponentClick}
onComponentOptionChanged={onComponentOptionChanged}
currentState={currentState}
onEvent={onEvent}
id={id}
paramUpdated={paramUpdated}
width={width}
changeCanDrag={changeCanDrag}
onComponentOptionsChanged={onComponentOptionsChanged}
height={height}
component={component}
containerProps={containerProps}
darkMode={darkMode}
removeComponent={removeComponent}
></ComponentToRender>
) : (
<div className="m-1" style={{ height: '100%' }}>
<div
className="component-image-holder p-2 d-flex flex-column justify-content-center"
style={{ height: '100%' }}
>
<center>
<div
style={{
width: '20px',
height: '20px',
backgroundSize: 'contain',
backgroundImage: `url(/assets/images/icons/widgets/${component.name.toLowerCase()}.svg)`,
backgroundRepeat: 'no-repeat',
}}
></div>
</center>
<span className="component-title">{component.displayName}</span>
</div>
</div>
</div>
)}
</div>
)}
</div>
</OverlayTrigger>
);
};

View file

@ -11,7 +11,7 @@ export const BoxDragPreview = memo(function BoxDragPreview({ item, currentLayout
[tickTock]
);
const layouts = item.layouts;
const layouts = item.layouts;
let { width, height } = layouts ? item.layouts[currentLayout] : {};
if (item.id === undefined) {
@ -21,9 +21,14 @@ export const BoxDragPreview = memo(function BoxDragPreview({ item, currentLayout
return (
<div style={{ height, width, border: 'solid 1px rgb(70, 165, 253)' }}>
<div style={{
background: '#438fd7', opacity: '0.7', height: '100%', width: '100%'
}}></div>
<div
style={{
background: '#438fd7',
opacity: '0.7',
height: '100%',
width: '100%',
}}
></div>
</div>
);
});

View file

@ -2,13 +2,11 @@ import React, { useState } from 'react';
import CodeMirror from '@uiw/react-codemirror';
import 'codemirror/theme/duotone-light.css';
import { componentTypes } from '../Components/components';
import { DataSourceTypes } from '../DataSourceManager/DataSourceTypes';
import { DataSourceTypes } from '../DataSourceManager/SourceComponents';
import { debounce } from 'lodash';
import Fuse from 'fuse.js';
export function CodeBuilder({
initialValue, onChange, components, dataQueries
}) {
export function CodeBuilder({ initialValue, onChange, components, dataQueries }) {
const [showDropdown, setShowDropdown] = useState(false);
const [cursorPosition, setCursorPosition] = useState(0);
const [currentValue, setCurrentValue] = useState(initialValue);
@ -130,7 +128,7 @@ export function CodeBuilder({
mode: 'javascript',
lineWrapping: true,
scrollbarStyle: null,
lineNumbers: false
lineNumbers: false,
}}
/>
{showDropdown && (

View file

@ -57,10 +57,12 @@ export function CodeHinter({
});
useEffect(() => {
setRealState(currentState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentState.components]);
let suggestions = useMemo(() => {
return getSuggestionKeys(realState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [realState.components, realState.queries]);
function valueChanged(editor, onChange, suggestions, ignoreBraces) {

View file

@ -1,33 +1,27 @@
import * as React from 'react'
import { mount } from '@cypress/react'
import { CodeHinter } from './CodeHinter'
import * as React from 'react';
import { mount } from '@cypress/react';
import { CodeHinter } from './CodeHinter';
it('Codehinter', () => {
mount(<CodeHinter
currentState={{
queries: {
postgres: {
data: []
}
},
components: {
mount(
<CodeHinter
currentState={{
queries: {
postgres: {
data: [],
},
},
components: {},
globals: {},
}}
initialValue={''}
theme="duotone-light"
mode="javascript"
lineNumbers={true}
className="query-hinter"
onChange={(value) => console.log(value)}
/>
);
},
globals: {
}
}}
initialValue={''}
theme="duotone-light"
mode="javascript"
lineNumbers={true}
className="query-hinter"
onChange={(value) => {}}
/>)
cy.get('.code-hinter')
.click()
.type('{{')
.contains('{{}}') // autocomplete for dynamic variables
})
cy.get('.code-hinter').click().type('{{').contains('{{}}'); // autocomplete for dynamic variables
});

View file

@ -6,16 +6,15 @@ export function getSuggestionKeys(currentState) {
_.keys(currentState).forEach((key) => {
_.keys(currentState[key]).forEach((key2) => {
_.keys(currentState[key][key2]).forEach((key3) => {
suggestions.push(`${key}.${key2}.${key3}`)
})
})
suggestions.push(`${key}.${key2}.${key3}`);
});
});
});
return suggestions;
}
export function generateHints(word, suggestions) {
if(word === '') {
if (word === '') {
return suggestions;
}
@ -38,25 +37,25 @@ export function computeCurrentWord(editor, _cursorPosition, ignoreBraces = false
export function makeOverlay(style) {
return {
// eslint-disable-next-line no-unused-vars
token: function (stream, state) {
var ch;
if (stream.match("{{")) {
if (stream.match('{{')) {
while ((ch = stream.next()) != null)
if (ch == "}" && stream.next() == "}") {
stream.eat("}");
if (ch == '}' && stream.next() == '}') {
stream.eat('}');
return style;
}
}
while (stream.next() != null && !stream.match("{{", false)) { }
// eslint-disable-next-line no-empty
while (stream.next() != null && !stream.match('{{', false)) {}
return null;
}
}
},
};
}
export function onBeforeChange(editor, change, ignoreBraces = false) {
if(!ignoreBraces) {
if (!ignoreBraces) {
const cursor = editor.getCursor();
const line = cursor.line;
const ch = cursor.ch;
@ -64,33 +63,30 @@ export function onBeforeChange(editor, change, ignoreBraces = false) {
const isLastCharacterBrace = value.slice(ch - 1, value.length) === '{';
if (isLastCharacterBrace && change.origin === '+input' && change.text[0] === '{') {
change.text[0] = '{}}'
change.text[0] = '{}}';
// editor.setCursor({ line: 0, ch: ch })
}
}
return change;
}
export function canShowHint(editor, ignoreBraces = false) {
if(!editor.hasFocus()) return false;
if (!editor.hasFocus()) return false;
const cursor = editor.getCursor();
const line = cursor.line;
const ch = cursor.ch;
const value = editor.getLine(line);
if(ignoreBraces && value.length > 0) return true;
if (ignoreBraces && value.length > 0) return true;
return value.slice(ch, ch + 2) === '}}';
}
export function handleChange(editor, onChange, suggestions, ignoreBraces = false) {
let state = editor.state.matchHighlighter;
editor.addOverlay(state.overlay = makeOverlay(state.options.style));
editor.addOverlay((state.overlay = makeOverlay(state.options.style)));
const cursor = editor.getCursor();
const currentWord = computeCurrentWord(editor, cursor.ch, ignoreBraces);
@ -103,11 +99,11 @@ export function handleChange(editor, onChange, suggestions, ignoreBraces = false
return {
from: { line: cursor.line, ch: cursor.ch - currentWord.length },
to: cursor,
list: hints
}
}
list: hints,
};
},
};
if (canShowHint(editor, ignoreBraces)) {
editor.showHint(options);
}
};
}

View file

@ -1,10 +1,8 @@
import React, { useState, useEffect } from 'react';
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
var tinycolor = require("tinycolor2");
var tinycolor = require('tinycolor2');
export const Button = function Button({
id, width, height, component, onComponentClick, currentState
}) {
export const Button = function Button({ id, width, height, component, onComponentClick, currentState }) {
console.log('currentState', currentState);
const [loadingState, setLoadingState] = useState(false);
@ -15,6 +13,7 @@ export const Button = function Button({
const newState = resolveReferences(loadingStateProperty.value, currentState, false);
setLoadingState(newState);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentState]);
const text = component.definition.properties.text.value;
@ -23,12 +22,15 @@ export const Button = function Button({
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
const computedStyles = {
backgroundColor,
@ -36,7 +38,7 @@ export const Button = function Button({
width,
height,
display: parsedWidgetVisibility ? '' : 'none',
'--tblr-btn-color-darker': tinycolor(backgroundColor).darken(8).toString()
'--tblr-btn-color-darker': tinycolor(backgroundColor).darken(8).toString(),
};
return (
@ -44,7 +46,10 @@ export const Button = function Button({
disabled={parsedDisabledState}
className={`jet-button btn btn-primary p-1 ${loadingState === true ? ' btn-loading' : ''}`}
style={computedStyles}
onClick={(event) => {event.stopPropagation(); onComponentClick(id, component)}}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
{text}
</button>

View file

@ -4,14 +4,9 @@ import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
// Use plotly basic bundle
import Plotly from 'plotly.js-basic-dist-min';
import createPlotlyComponent from 'react-plotly.js/factory';
const Plot = createPlotlyComponent(Plotly)
import Skeleton from 'react-loading-skeleton';
export const Chart = function Chart({
id, width, height, component, onComponentClick, currentState, darkMode
}) {
const Plot = createPlotlyComponent(Plotly);
export const Chart = function Chart({ id, width, height, component, onComponentClick, currentState, darkMode }) {
const [loadingState, setLoadingState] = useState(false);
const [chartData, setChartData] = useState([]);
@ -19,28 +14,31 @@ export const Chart = function Chart({
const disabledState = component.definition.styles?.disabledState?.value ?? false;
let parsedWidgetVisibility = widgetVisibility;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
useEffect(() => {
const loadingStateProperty = component.definition.properties.loadingState;
if (loadingStateProperty && currentState) {
const newState = resolveReferences(loadingStateProperty.value, currentState, false);
setLoadingState(newState);
setLoadingState(newState);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentState]);
const computedStyles = {
width,
height,
display: parsedWidgetVisibility ? '' : 'none',
background: darkMode ? '#1f2936' : 'white'
background: darkMode ? '#1f2936' : 'white',
};
// darkMode ? '#1f2936' : 'white'
const dataProperty = component.definition.properties.data;
const dataString = dataProperty ? dataProperty.value : [];
@ -66,73 +64,84 @@ export const Chart = function Chart({
title: {
text: title,
font: {
color: fontColor
}
color: fontColor,
},
},
legend: {
text: title,
font: {
color: fontColor
}
color: fontColor,
},
},
xaxis: {
showgrid: showGridLines,
showline: true,
color: fontColor
color: fontColor,
},
yaxis: {
showgrid: showGridLines,
showline: true,
color: fontColor
}
}
showgrid: showGridLines,
showline: true,
color: fontColor,
},
};
const data = resolveReferences(dataString, currentState, []);
useEffect(() => {
let rawData = data || [];
if(typeof rawData === 'string') {
if (typeof rawData === 'string') {
try {
rawData = JSON.parse(dataString);
} catch (err) { rawData = []; }
} catch (err) {
rawData = [];
}
}
if(!Array.isArray(rawData)) { rawData = []; }
if (!Array.isArray(rawData)) {
rawData = [];
}
let newData = [];
if(chartType === 'pie') {
newData = [{
type: chartType,
values: rawData.map((item) => item["value"]),
labels: rawData.map((item) => item["label"]),
}];
if (chartType === 'pie') {
newData = [
{
type: chartType,
values: rawData.map((item) => item['value']),
labels: rawData.map((item) => item['label']),
},
];
} else {
newData = [{
type: chartType || 'line',
x: rawData.map((item) => item["x"]),
y: rawData.map((item) => item["y"]),
marker: { color: markerColor }
}];
newData = [
{
type: chartType || 'line',
x: rawData.map((item) => item['x']),
y: rawData.map((item) => item['y']),
marker: { color: markerColor },
},
];
}
setChartData(newData);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, chartType]);
return (
<div
data-disabled={parsedDisabledState}
style={computedStyles}
onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
{loadingState === true ?
{loadingState === true ? (
<div style={{ width: '100%' }} className="p-2">
<center>
<div className="spinner-border mt-5" role="status"></div>
</center>
</div>
:
) : (
<Plot
data={chartData}
layout={layout}
@ -141,7 +150,7 @@ export const Chart = function Chart({
// staticPlot: true
}}
/>
}
)}
</div>
);
};

View file

@ -9,22 +9,24 @@ export const Checkbox = function Checkbox({
onComponentClick,
currentState,
onComponentOptionChanged,
onEvent
onEvent,
}) {
const label = component.definition.properties.label.value;
const textColorProperty = component.definition.styles.textColor;
const textColor = textColorProperty ? textColorProperty.value : '#000';
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
function toggleValue(e) {
const checked = e.target.checked;
@ -37,7 +39,15 @@ export const Checkbox = function Checkbox({
}
return (
<div className="row" style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<div
data-disabled={parsedDisabledState}
className="row"
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
<label className="my-auto mx-2 form-check form-check-inline">
<input
className="form-check-input"
@ -46,7 +56,9 @@ export const Checkbox = function Checkbox({
toggleValue(e);
}}
/>
<span className="form-check-label" style={{color: textColor}}>{label}</span>
<span className="form-check-label" style={{ color: textColor }}>
{label}
</span>
</label>
</div>
);

View file

@ -10,43 +10,42 @@ export const Container = function Container({
containerProps,
width,
currentState,
removeComponent
removeComponent,
}) {
const backgroundColor = component.definition.styles.backgroundColor.value;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
const computedStyles = {
backgroundColor,
width,
height,
display: parsedWidgetVisibility ? 'flex' : 'none'
display: parsedWidgetVisibility ? 'flex' : 'none',
};
const parentRef = useRef(null);
return (
<div data-disabled={parsedDisabledState} className="jet-container" ref={parentRef} onClick={() => containerProps.onComponentClick(id, component)} style={computedStyles}>
<SubContainer
parent={id}
{...containerProps}
parentRef={parentRef}
removeComponent={removeComponent}
/>
<SubCustomDragLayer
parent={id}
parentRef={parentRef}
currentLayout={containerProps.currentLayout}
/>
<div
data-disabled={parsedDisabledState}
className="jet-container"
ref={parentRef}
onClick={() => containerProps.onComponentClick(id, component)}
style={computedStyles}
>
<SubContainer parent={id} {...containerProps} parentRef={parentRef} removeComponent={removeComponent} />
<SubCustomDragLayer parent={id} parentRef={parentRef} currentLayout={containerProps.currentLayout} />
</div>
);
};

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';
import { resolveReferences, resolveWidgetFieldValue, validateWidget } from '@/_helpers/utils';
@ -10,7 +10,7 @@ export const Datepicker = function Datepicker({
component,
onComponentClick,
currentState,
onComponentOptionChanged
onComponentOptionChanged,
}) {
console.log('currentState', currentState);
@ -19,55 +19,89 @@ export const Datepicker = function Datepicker({
const enableDateProp = component.definition.properties.enableDate;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const defaultValue = component.definition.properties?.defaultValue?.value ?? '';
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
const enableTime = resolveReferences(enableTimeProp.value, currentState, false);
let enableDate = true;
if (enableDateProp) {
// eslint-disable-next-line no-unused-vars
enableDate = resolveReferences(enableDateProp.value, currentState, true);
}
let dateFormat = formatProp
let dateFormat = formatProp;
try {
dateFormat = resolveReferences(formatProp, currentState);
} catch (err) { console.log(err); }
function onDateChange(event) {
onComponentOptionChanged(component, 'value', event.format(dateFormat.value));
} catch (err) {
console.log(err);
}
const value = currentState?.components[component?.name]?.value;
function onDateChange(event) {
const value = event._isAMomentObject ? event.format(dateFormat.value) : event;
setDateText(value);
onComponentOptionChanged(component, 'value', value);
}
let value = defaultValue;
if (value && currentState) value = resolveReferences(value, currentState, '');
const [dateText, setDateText] = useState(value);
useEffect(() => {
setDateText(value);
onComponentOptionChanged(component, 'value', value);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
const validationData = validateWidget({
validationObject: component.definition.validation,
widgetValue: value,
currentState
})
currentState,
});
const { isValid, validationError } = validationData;
const currentValidState = currentState?.components[component?.name]?.isValid;
if(currentValidState !== isValid) {
if (currentValidState !== isValid) {
onComponentOptionChanged(component, 'isValid', isValid);
}
return (
<div data-disabled={parsedDisabledState} style={{ width, height, display:parsedWidgetVisibility ? '' : 'none'}} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<Datetime
onChange={onDateChange}
timeFormat={enableTime}
<div
data-disabled={parsedDisabledState}
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
<Datetime
onChange={onDateChange}
timeFormat={enableTime}
closeOnSelect={true}
dateFormat={dateFormat.value}
dateFormat={dateFormat.value}
value={dateText}
renderInput={(props) => {
return (
<input
{...props}
value={dateText}
className={`form-control ${!isValid ? 'is-invalid' : ''} validation-without-icon`}
/>
);
}}
/>
<div className={`invalid-feedback ${isValid ? '' : 'd-flex'}`}>{validationError}</div>
</div>

View file

@ -12,7 +12,7 @@ export const DaterangePicker = function DaterangePicker({
component,
onComponentClick,
currentState,
onComponentOptionChanged
onComponentOptionChanged,
}) {
console.log('currentState', currentState);
@ -21,18 +21,20 @@ export const DaterangePicker = function DaterangePicker({
const formatProp = component.definition.properties.format;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const [focusedInput, setFocusedInput] = useState(null);
const [startDate, setStartDate] = useState(startDateProp ? startDateProp.value : null);
const [endDate, setEndDate] = useState(endDateProp ? endDateProp.value : null);
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
function onDateChange(dates) {
const start = dates.startDate;
@ -55,7 +57,13 @@ export const DaterangePicker = function DaterangePicker({
}
return (
<div style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<div
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
<DateRangePicker
disabled={parsedDisabledState}
startDate={startDate}

View file

@ -1,3 +1,4 @@
/* eslint-disable react/no-string-refs */
import React from 'react';
import { Editor, EditorState, RichUtils, getDefaultKeyBinding } from 'draft-js';
import 'draft-js/dist/Draft.css';

View file

@ -10,7 +10,7 @@ export const DropDown = function DropDown({
onComponentClick,
currentState,
onComponentOptionChanged,
onEvent
onEvent,
}) {
console.log('currentState', currentState);
@ -20,25 +20,32 @@ export const DropDown = function DropDown({
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedValues = values;
try {
parsedValues = resolveReferences(values, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
let parsedDisplayValues = displayValues;
try {
parsedDisplayValues = resolveReferences(displayValues, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
let selectOptions = [];
@ -46,14 +53,15 @@ export const DropDown = function DropDown({
selectOptions = [
...parsedValues.map((value, index) => {
return { name: parsedDisplayValues[index], value: value };
})
}),
];
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
const currentValueProperty = component.definition.properties.value;
const value = currentValueProperty ? currentValueProperty.value : '';
const [currentValue, setCurrentValue] = useState('');
let newValue = value;
if (currentValueProperty && currentState) {
@ -63,14 +71,14 @@ export const DropDown = function DropDown({
const validationData = validateWidget({
validationObject: component.definition.validation,
widgetValue: currentValue,
currentState
})
currentState,
});
const { isValid, validationError } = validationData;
const currentValidState = currentState?.components[component?.name]?.isValid;
if(currentValidState !== isValid) {
if (currentValidState !== isValid) {
onComponentOptionChanged(component, 'isValid', isValid);
}
@ -79,13 +87,23 @@ export const DropDown = function DropDown({
}, [newValue]);
useEffect(() => {
onComponentOptionChanged(component, 'value', currentValue).then(() => onEvent('onSelect', { component }) );
onComponentOptionChanged(component, 'value', currentValue).then(() => onEvent('onSelect', { component }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentValue]);
return (
<div className="dropdown-widget row g-0" style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<div
className="dropdown-widget row g-0"
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
<div className="col-auto">
<label style={{marginRight: label !== '' ? '1rem' : '0.001rem'}} className="form-label py-1">{label}</label>
<label style={{ marginRight: label !== '' ? '1rem' : '0.001rem' }} className="form-label py-1">
{label}
</label>
</div>
<div className="col px-0">
<SelectSearch

View file

@ -2,33 +2,39 @@ import React from 'react';
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
import LazyLoad from 'react-lazyload';
export const Image = function Image({
id, width, height, component, onComponentClick, currentState
}) {
export const Image = function Image({ id, width, height, component, onComponentClick, currentState }) {
const source = component.definition.properties.source.value;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let data = resolveReferences(source, currentState, null);
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
if (data === '') data = null;
function Placeholder() {
return (
<div className="skeleton-image" style={{ objectFit: 'contain', width, height }}></div>
);
return <div className="skeleton-image" style={{ objectFit: 'contain', width, height }}></div>;
}
return (
<div data-disabled={parsedDisabledState} style={{display:parsedWidgetVisibility ? '' : 'none'}} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<LazyLoad width={width} height={height} placeholder={<Placeholder/>} debounce={500}>
<div
data-disabled={parsedDisabledState}
style={{ display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
<LazyLoad width={width} height={height} placeholder={<Placeholder />} debounce={500}>
<img style={{ objectFit: 'contain' }} src={data} width={width} height={height} />
</LazyLoad>
</div>

View file

@ -1,8 +1,6 @@
import React, { useEffect, useState, useCallback } from 'react';
import { GoogleMap, LoadScript } from '@react-google-maps/api';
import { Marker } from '@react-google-maps/api';
import React, { useState, useCallback } from 'react';
import { GoogleMap, LoadScript, Marker, Autocomplete } from '@react-google-maps/api';
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
import { Autocomplete } from '@react-google-maps/api';
import { darkModeStyles } from './styles';
export const Map = function Map({
@ -88,6 +86,7 @@ export const Map = function Map({
]).then(() => onEvent('onBoundsChange', { component }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const onLoad = useCallback(function onLoad(mapInstance) {
setGmap(mapInstance);
onComponentOptionsChanged(component, [['center', mapInstance.center?.toJSON()]]);
@ -151,7 +150,7 @@ export const Map = function Map({
{Array.isArray(markers) && (
<>
{markers.map((marker, index) => (
<Marker position={marker} label={marker.label} onClick={(e) => handleMarkerClick(index)} />
<Marker key={index} position={marker} label={marker.label} onClick={() => handleMarkerClick(index)} />
))}
</>
)}

View file

@ -1,137 +1,137 @@
export const darkModeStyles = [
{
"featureType": "all",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#ffffff"
}
]
},
{
"featureType": "all",
"elementType": "labels.text.stroke",
"stylers": [
{
"color": "#000000"
},
{
"lightness": 13
}
]
},
{
"featureType": "administrative",
"elementType": "geometry.fill",
"stylers": [
{
"color": "#000000"
}
]
},
{
"featureType": "administrative",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#144b53"
},
{
"lightness": 14
},
{
"weight": 1.4
}
]
},
{
"featureType": "landscape",
"elementType": "all",
"stylers": [
{
"color": "#08304b"
}
]
},
{
"featureType": "poi",
"elementType": "geometry",
"stylers": [
{
"color": "#0c4152"
},
{
"lightness": 5
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry.fill",
"stylers": [
{
"color": "#000000"
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#0b434f"
},
{
"lightness": 25
}
]
},
{
"featureType": "road.arterial",
"elementType": "geometry.fill",
"stylers": [
{
"color": "#000000"
}
]
},
{
"featureType": "road.arterial",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#0b3d51"
},
{
"lightness": 16
}
]
},
{
"featureType": "road.local",
"elementType": "geometry",
"stylers": [
{
"color": "#000000"
}
]
},
{
"featureType": "transit",
"elementType": "all",
"stylers": [
{
"color": "#146474"
}
]
},
{
"featureType": "water",
"elementType": "all",
"stylers": [
{
"color": "#021019"
}
]
}
];
{
featureType: 'all',
elementType: 'labels.text.fill',
stylers: [
{
color: '#ffffff',
},
],
},
{
featureType: 'all',
elementType: 'labels.text.stroke',
stylers: [
{
color: '#000000',
},
{
lightness: 13,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.fill',
stylers: [
{
color: '#000000',
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.stroke',
stylers: [
{
color: '#144b53',
},
{
lightness: 14,
},
{
weight: 1.4,
},
],
},
{
featureType: 'landscape',
elementType: 'all',
stylers: [
{
color: '#08304b',
},
],
},
{
featureType: 'poi',
elementType: 'geometry',
stylers: [
{
color: '#0c4152',
},
{
lightness: 5,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.fill',
stylers: [
{
color: '#000000',
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.stroke',
stylers: [
{
color: '#0b434f',
},
{
lightness: 25,
},
],
},
{
featureType: 'road.arterial',
elementType: 'geometry.fill',
stylers: [
{
color: '#000000',
},
],
},
{
featureType: 'road.arterial',
elementType: 'geometry.stroke',
stylers: [
{
color: '#0b3d51',
},
{
lightness: 16,
},
],
},
{
featureType: 'road.local',
elementType: 'geometry',
stylers: [
{
color: '#000000',
},
],
},
{
featureType: 'transit',
elementType: 'all',
stylers: [
{
color: '#146474',
},
],
},
{
featureType: 'water',
elementType: 'all',
stylers: [
{
color: '#021019',
},
],
},
];

View file

@ -4,16 +4,9 @@ import Button from 'react-bootstrap/Button';
import { SubCustomDragLayer } from '../SubCustomDragLayer';
import { SubContainer } from '../SubContainer';
import { ConfigHandle } from '../ConfigHandle';
import { resolveWidgetFieldValue, resolveReferences } from '../../_helpers/utils';
import { resolveWidgetFieldValue } from '../../_helpers/utils';
export const Modal = function Modal({
id,
component,
height,
mode,
containerProps,
currentState
}) {
export const Modal = function Modal({ id, component, height, containerProps, currentState }) {
const [show, showModal] = useState(false);
const parentRef = useRef(null);
@ -25,12 +18,14 @@ export const Modal = function Modal({
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
useEffect(() => {
const componentState = containerProps.currentState.components[component.name];
const canShowModel = componentState ? componentState.show : false;
showModal(canShowModel);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [containerProps.currentState.components[component.name]]);
function hideModal() {
@ -51,17 +46,11 @@ export const Modal = function Modal({
animation={false}
onEscapeKeyDown={() => showModal(false)}
>
{containerProps.mode === 'edit' &&
<ConfigHandle
id={id}
component={component}
configHandleClicked={containerProps.onComponentClick}
/>
}
{containerProps.mode === 'edit' && (
<ConfigHandle id={id} component={component} configHandleClicked={containerProps.onComponentClick} />
)}
<BootstrapModal.Header>
<BootstrapModal.Title>
{title}
</BootstrapModal.Title>
<BootstrapModal.Title>{title}</BootstrapModal.Title>
<div>
<Button variant="light" size="sm" onClick={hideModal}>
x
@ -70,17 +59,13 @@ export const Modal = function Modal({
</BootstrapModal.Header>
<BootstrapModal.Body style={{ height }} ref={parentRef}>
<SubContainer
parent={id}
{...containerProps}
parentRef={parentRef}
/>
<SubCustomDragLayer
snapToGrid={true}
parentRef={parentRef}
parent={id}
currentLayout={containerProps.currentLayout}
/>
<SubContainer parent={id} {...containerProps} parentRef={parentRef} />
<SubCustomDragLayer
snapToGrid={true}
parentRef={parentRef}
parent={id}
currentLayout={containerProps.currentLayout}
/>
</BootstrapModal.Body>
</BootstrapModal>
</div>

View file

@ -9,7 +9,7 @@ export const Multiselect = function Multiselect({
component,
onComponentClick,
currentState,
onComponentOptionChanged
onComponentOptionChanged,
}) {
console.log('currentState', currentState);
@ -19,7 +19,8 @@ export const Multiselect = function Multiselect({
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedValues = JSON.parse(values);
const parsedDisplayValues = JSON.parse(displayValues);
@ -27,7 +28,7 @@ export const Multiselect = function Multiselect({
const selectOptions = [
...parsedValues.map((value, index) => {
return { name: parsedDisplayValues[index], value: value };
})
}),
];
const currentValueProperty = component.definition.properties.values;
@ -40,19 +41,30 @@ export const Multiselect = function Multiselect({
}
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
useEffect(() => {
setCurrentValue(newValue);
}, [newValue]);
return (
<div className="multiselect-widget row g-0" style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<div
className="multiselect-widget row g-0"
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
<div className="col-auto">
<label style={{marginRight: '1rem'}} className="form-label py-1">{label}</label>
<label style={{ marginRight: '1rem' }} className="form-label py-1">
{label}
</label>
</div>
<div className="col px-0">
<SelectSearch

View file

@ -8,9 +8,8 @@ export const NumberInput = function NumberInput({
component,
onComponentClick,
currentState,
onComponentOptionChanged
onComponentOptionChanged,
}) {
const value = component.definition.properties.value ? component.definition.properties.value.value : '';
const [number, setNumber] = useState(value);
@ -23,24 +22,31 @@ export const NumberInput = function NumberInput({
useEffect(() => {
setNumber(parseInt(newNumber));
onComponentOptionChanged(component, 'value', parseInt(newNumber));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [newNumber]);
const placeholder = component.definition.properties.placeholder.value;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
return (
<input
disabled={parsedDisabledState}
onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
onChange={(e) => {
setNumber(parseInt(e.target.value));
onComponentOptionChanged(component, 'value', parseInt(e.target.value));
@ -48,7 +54,7 @@ export const NumberInput = function NumberInput({
type="number"
className="form-control"
placeholder={placeholder}
style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }}
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
value={number}
/>
);

View file

@ -1,34 +1,41 @@
import React, { useState } from 'react';
import React from 'react';
export default function ErrorModal() {
const [show, setShow] = React.useState(true)
const [show, setShow] = React.useState(true);
const close = () => {
setShow(false)
}
return(
setShow(false);
};
return (
<div>
{
show ?
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">QR Scanner is not working</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onClick={close}></button>
</div>
<div class="modal-body">
Please make sure a camera is available on your device. Try closing your browser and opening it again, if it doesn't work, please contact support.
</div>
<div class="modal-footer">
<button type="button" class="btn" data-bs-dismiss="modal" onClick={close}>Close</button>
</div>
{show ? (
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">QR Scanner is not working</h5>
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
onClick={close}
></button>
</div>
<div className="modal-body">
Please make sure a camera is available on your device. Try closing your browser and opening it again, if
it doesn&apos;t work, please contact support.
</div>
<div className="modal-footer">
<button type="button" className="btn" data-bs-dismiss="modal" onClick={close}>
Close
</button>
</div>
</div>
:
''
}
</div>
) : (
''
)}
</div>
);
};
}

View file

@ -3,10 +3,7 @@ import QrReader from 'react-qr-reader';
import ErrorModal from './ErrorModal';
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
export const QrScanner = function QrScanner({
component, onEvent, onComponentOptionChanged, currentState
}) {
export const QrScanner = function QrScanner({ component, onEvent, onComponentOptionChanged, currentState }) {
const handleError = async (errorMessage) => {
console.log(errorMessage);
setErrorOccured(true);
@ -16,7 +13,7 @@ export const QrScanner = function QrScanner({
if (data != null) {
onEvent('onDetect', { component, data: data });
onComponentOptionChanged(component, 'lastDetectedValue', data);
};
}
};
let [errorOccured, setErrorOccured] = useState(false);
@ -24,25 +21,20 @@ export const QrScanner = function QrScanner({
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
return (
<div data-disabled={parsedDisabledState} style={{display:parsedWidgetVisibility ? '' : 'none'}}>
{
errorOccured ?
<ErrorModal />
:
<QrReader
onError={handleError}
onScan={handleScan}
/>
}
<div data-disabled={parsedDisabledState} style={{ display: parsedWidgetVisibility ? '' : 'none' }}>
{errorOccured ? <ErrorModal /> : <QrReader onError={handleError} onScan={handleScan} />}
</div>
);
};

View file

@ -1,7 +1,6 @@
import React from 'react';
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
export const RadioButton = function RadioButton({
id,
width,
@ -10,9 +9,8 @@ export const RadioButton = function RadioButton({
onComponentClick,
currentState,
onComponentOptionChanged,
onEvent
onEvent,
}) {
const label = component.definition.properties.label.value;
const textColorProperty = component.definition.styles.textColor;
const textColor = textColorProperty ? textColorProperty.value : '#000';
@ -23,25 +21,32 @@ export const RadioButton = function RadioButton({
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedValues = values;
try {
parsedValues = resolveReferences(values, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
let parsedDisplayValues = displayValues;
try {
parsedDisplayValues = resolveReferences(displayValues, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
let parsedDefaultValue = defaultValue;
try {
parsedDefaultValue = resolveReferences(defaultValue, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
let selectOptions = [];
@ -49,33 +54,54 @@ export const RadioButton = function RadioButton({
selectOptions = [
...parsedValues.map((value, index) => {
return { name: parsedDisplayValues[index], value: value };
})
}),
];
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
function onSelect(event) {
const selection = event.target.value
const selection = event.target.value;
onComponentOptionChanged(component, 'value', selection);
if (selection) {
onEvent('onSelectionChange', { component });
}
}
}
return (
<div data-disabled={parsedDisabledState} className="row" style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<span className="form-check-label form-check-label col-auto py-1" style={{color: textColor}}>{label}</span>
<div
data-disabled={parsedDisabledState}
className="row"
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
<span className="form-check-label form-check-label col-auto py-1" style={{ color: textColor }}>
{label}
</span>
<div className="col py-1" onChange={(e) => onSelect(e)}>
{selectOptions.map((option, index) => (
<label key={index} class="form-check form-check-inline">
<input class="form-check-input" defaultChecked={parsedDefaultValue === option.value} type="radio" value={option.value} name="radio-options" />
<span className="form-check-label" style={{color: textColor}}>{option.name}</span>
<label key={index} className="form-check form-check-inline">
<input
className="form-check-input"
defaultChecked={parsedDefaultValue === option.value}
type="radio"
value={option.value}
name="radio-options"
/>
<span className="form-check-label" style={{ color: textColor }}>
{option.name}
</span>
</label>
))}
</div>

View file

@ -1,6 +1,5 @@
import React from 'react';
import { Editor, EditorState } from "draft-js";
import "draft-js/dist/Draft.css";
import 'draft-js/dist/Draft.css';
import { DraftEditor } from './DraftEditor';
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
@ -11,33 +10,37 @@ export const RichTextEditor = function RichTextEditor({
component,
onComponentClick,
currentState,
onComponentOptionChanged
onComponentOptionChanged,
}) {
const placeholder = component.definition.properties.placeholder.value;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
function handleChange(html) {
onComponentOptionChanged(component, 'value', html);
}
return (
<div data-disabled={parsedDisabledState} style={{ width: `${width}px`, height: `${height}px`, display:parsedWidgetVisibility ? '' : 'none' }} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<DraftEditor
handleChange={handleChange}
height={height}
width={width}
placeholder={placeholder}
></DraftEditor>
<div
data-disabled={parsedDisabledState}
style={{ width: `${width}px`, height: `${height}px`, display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
<DraftEditor handleChange={handleChange} height={height} width={width} placeholder={placeholder}></DraftEditor>
</div>
);
};

View file

@ -1,47 +1,44 @@
import React from 'react'
import React from 'react';
export default ({ fill = '#ffb400'}) => {
export default ({ fill = '#ffb400' }) => {
return (
<svg height="20" width="20" fill={fill} version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 630 630" xmlSpace="preserve">
<path d="M610.089,233.999c-4.591-14.132-16.807-24.432-31.512-26.567l-164.157-23.856L341.006,34.827
<svg
height="20"
width="20"
fill={fill}
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 630 630"
xmlSpace="preserve"
>
<path
d="M610.089,233.999c-4.591-14.132-16.807-24.432-31.512-26.567l-164.157-23.856L341.006,34.827
c-6.577-13.325-20.147-21.76-35.005-21.76c-14.86,0-28.43,8.435-35.005,21.76l-73.416,148.749L33.424,207.432
c-14.705,2.136-26.921,12.436-31.512,26.567s-0.762,29.645,9.879,40.017l118.786,115.787l-28.043,163.492
c-2.513,14.646,3.507,29.446,15.529,38.18c12.021,8.734,27.958,9.885,41.112,2.972l146.825-77.192l146.825,77.192
c5.713,3.004,11.949,4.485,18.162,4.485c8.092,0,16.149-2.515,22.95-7.455c12.021-8.734,18.041-23.536,15.529-38.18
l-28.041-163.494l118.784-115.789C610.851,263.643,614.68,248.131,610.089,233.999z M412.235,348.222
c-9.202,8.967-13.399,21.889-11.227,34.552l18.139,105.762l-94.979-49.934c-3.162-1.664-6.498-2.847-9.909-3.584V157.04
l39.232,79.493c5.686,11.522,16.676,19.508,29.391,21.354l106.192,15.431L412.235,348.222z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
l39.232,79.493c5.686,11.522,16.676,19.508,29.391,21.354l106.192,15.431L412.235,348.222z"
/>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
</svg>
)
}
);
};

View file

@ -1,44 +1,41 @@
import React from 'react'
import React from 'react';
export default ({ fill = '#ffb400'}) => {
export default ({ fill = '#ffb400' }) => {
return (
<svg height="20" width="20" fill={fill} version="1.1" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 500 500" xmlSpace="preserve">
<path d="M471.563,173.778l-145.5-20.8l-64.4-132c-8-15.4-30-12.2-35.3,0l-64.4,132l-145.6,20.8c-16.4,1-21.6,20.9-10.4,33.2
<svg
height="20"
width="20"
fill={fill}
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 500 500"
xmlSpace="preserve"
>
<path
d="M471.563,173.778l-145.5-20.8l-64.4-132c-8-15.4-30-12.2-35.3,0l-64.4,132l-145.6,20.8c-16.4,1-21.6,20.9-10.4,33.2
l105,102.9l-25,144.5c-2.9,17.8,16.7,27.8,28.1,20.8l129.9-68.6l129.9,67.6c13.6,7,29.8-2.8,28.1-19.7l-25-144.6l105-102.9
C494.663,193.478,485.563,175.478,471.563,173.778z M342.663,288.078c-4.2,5.2-6.2,11.4-5.2,17.7l19.7,116.4l-103.9-55.1
c-6.7-2.8-13-2.8-18.7,0l-103.9,55.1l19.7-116.4c1-7.3-1-13.5-5.2-17.7l-84.1-82.1l116.4-16.6c6.2-1,11.4-4.2,14.6-10.4l52-105
l52,105c3.1,5.2,8.3,9.4,14.6,10.4l116.2,16.6L342.663,288.078z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
l52,105c3.1,5.2,8.3,9.4,14.6,10.4l116.2,16.6L342.663,288.078z"
/>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
</svg>
)
}
);
};

View file

@ -1,44 +1,41 @@
import React from 'react'
import React from 'react';
export default ({ fill = '#ffb400'}) => {
export default ({ fill = '#ffb400' }) => {
return (
<svg version="1.1" fill={fill} height="20" width="20" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 60 60" xmlSpace="preserve">
<path d="M55.818,21.578c-0.118-0.362-0.431-0.626-0.808-0.681L36.92,18.268L28.83,1.876c-0.168-0.342-0.516-0.558-0.896-0.558
<svg
version="1.1"
fill={fill}
height="20"
width="20"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 60 60"
xmlSpace="preserve"
>
<path
d="M55.818,21.578c-0.118-0.362-0.431-0.626-0.808-0.681L36.92,18.268L28.83,1.876c-0.168-0.342-0.516-0.558-0.896-0.558
s-0.729,0.216-0.896,0.558l-8.091,16.393l-18.09,2.629c-0.377,0.055-0.689,0.318-0.808,0.681c-0.117,0.361-0.02,0.759,0.253,1.024
l13.091,12.76l-3.091,18.018c-0.064,0.375,0.09,0.754,0.397,0.978c0.309,0.226,0.718,0.255,1.053,0.076l16.182-8.506l16.18,8.506
c0.146,0.077,0.307,0.115,0.466,0.115c0.207,0,0.413-0.064,0.588-0.191c0.308-0.224,0.462-0.603,0.397-0.978l-3.09-18.017
l13.091-12.761C55.838,22.336,55.936,21.939,55.818,21.578z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
l13.091-12.761C55.838,22.336,55.936,21.939,55.818,21.578z"
/>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
<g></g>
</svg>
)
}
);
};

View file

@ -23,13 +23,16 @@ export const StarRating = function StarRating({
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
const tooltips = component.definition.properties.tooltips.value ?? [];
const _tooltips = resolveReferences(tooltips, currentState, []) ?? [];
@ -54,12 +57,14 @@ export const StarRating = function StarRating({
React.useEffect(() => {
setRatingIndex(defaultSelected - 1);
onComponentOptionChanged(component, 'value', defaultSelected);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultSelected]);
React.useEffect(() => {
setTimeout(() => {
onComponentOptionChanged(component, 'value', defaultSelected);
}, 1000)
}, 1000);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function handleClick(idx) {
@ -84,7 +89,15 @@ export const StarRating = function StarRating({
};
return (
<div data-disabled={parsedDisabledState} className="star-rating" onClick={event => {event.stopPropagation(); onComponentClick(id, component)}} style={{display:parsedWidgetVisibility ? '' : 'none'}}>
<div
data-disabled={parsedDisabledState}
className="star-rating"
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
style={{ display: parsedWidgetVisibility ? '' : 'none' }}
>
{/* TODO: Add label color defination property instead of hardcoded color*/}
<span className="label form-check-label form-check-label col-auto" style={{ color: '#000' }}>
{label}

View file

@ -37,6 +37,7 @@ const Star = ({
React.useEffect(() => {
setIcon(isHalfStar ? halfStar : star);
setOutlineIcon(isHalfStar ? halfStar : starOutline);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [color]);
const ref = React.useRef(null);

View file

@ -1,26 +1,29 @@
import React from 'react';
import SelectSearch from 'react-select-search';
export const CustomSelect = ({ options, value, multiple, disabled, onChange }) => {
function renderValue(valueProps) {
if(valueProps) {
return valueProps.value.split(', ').map((value) => <span {...valueProps} className="badge bg-blue-lt p-2 mx-1">{value}</span>);
}
export const CustomSelect = ({ options, value, multiple, onChange }) => {
function renderValue(valueProps) {
if (valueProps) {
return valueProps.value.split(', ').map((value, index) => (
<span key={index} {...valueProps} className="badge bg-blue-lt p-2 mx-1">
{value}
</span>
));
}
}
return (
<div className="custom-select">
<SelectSearch
options={options}
printOptions="on-focus"
value={value}
renderValue={renderValue}
search={false}
onChange={onChange}
multiple={multiple}
placeholder="Select.."
/>
</div>
);
};
return (
<div className="custom-select">
<SelectSearch
options={options}
printOptions="on-focus"
value={value}
renderValue={renderValue}
search={false}
onChange={onChange}
multiple={multiple}
placeholder="Select.."
/>
</div>
);
};

View file

@ -2,80 +2,72 @@ import React from 'react';
import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';
import '@/_styles/custom.scss';
import moment from 'moment'
import moment from 'moment';
export const Datepicker = function Datepicker({value, onChange, readOnly, isTimeChecked, dateFormat}) {
const [date, setDate] = React.useState(value)
export const Datepicker = function Datepicker({ value, onChange, readOnly, isTimeChecked, dateFormat }) {
const [date, setDate] = React.useState(value);
const dateChange = (e) => {
if(isTimeChecked) {
setDate(e.format(`${dateFormat} LT`))
} else {
setDate(e.format(dateFormat))
}
const dateChange = (e) => {
if (isTimeChecked) {
setDate(e.format(`${dateFormat} LT`));
} else {
setDate(e.format(dateFormat));
}
};
React.useEffect(() => {
if (!isTimeChecked) {
setDate(moment(value, 'DD-MM-YYYY').format(dateFormat));
}
React.useEffect(() => {
const _date = isTimeChecked ? moment(value, `${dateFormat} LT`) : moment(value, dateFormat)
if (isTimeChecked) {
setDate(moment(value, 'DD-MM-YYYY LT').format(`${dateFormat} LT`));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTimeChecked, readOnly, dateFormat]);
if(!isTimeChecked) {
setDate(moment(value, 'DD-MM-YYYY').format(dateFormat))
}
let inputProps = {
disabled: !readOnly,
};
if(isTimeChecked) {
setDate(moment(value, 'DD-MM-YYYY LT' ).format(`${dateFormat} LT`))
}
},[isTimeChecked, readOnly, dateFormat])
const [isDatepickerOpen, setIsDatepickerOpen] = React.useState(false);
const onDatepickerClose = () => {
onChange(date);
setIsDatepickerOpen((prev) => !prev);
};
let inputProps = {
disabled: !readOnly,
React.useEffect(() => {
const myElement = document.querySelector('.cell-type-datepicker');
myElement.parentNode.style.position = 'absolute';
myElement.style.position = 'relative';
myElement.style.marginTop = '2px';
myElement.style.left = '50%';
myElement.style.width = '250px';
myElement.style.transform = 'translate(-50%, -25%)';
return () => {
myElement.parentNode.style.position = '';
myElement.style.position = '';
myElement.style.marginTop = '';
myElement.style.left = '';
myElement.style.width = '';
myElement.style.transform = '';
};
}, [isDatepickerOpen]);
const [isDatepickerOpen, setIsDatepickerOpen] = React.useState(false)
const onDatepickerClose = () => {
onChange(date)
setIsDatepickerOpen((prev) => !prev)
}
React.useEffect(() => {
const myElement = document.querySelector('.cell-type-datepicker')
myElement.parentNode.style.position = 'absolute'
myElement.style.position = 'relative'
myElement.style.marginTop = '2px'
myElement.style.left = '50%'
myElement.style.width = '250px'
myElement.style.transform = 'translate(-50%, -25%)'
return () => {
myElement.parentNode.style.position = ''
myElement.style.position = ''
myElement.style.marginTop = ''
myElement.style.left = ''
myElement.style.width = ''
myElement.style.transform = ''
}
},[isDatepickerOpen])
return (
<>
<Datetime
inputProps={inputProps}
timeFormat={isTimeChecked}
className='cell-type-datepicker'
dateFormat={dateFormat}
value={date}
onChange={dateChange}
onClose={onDatepickerClose}
/>
</>
)
return (
<>
<Datetime
inputProps={inputProps}
timeFormat={isTimeChecked}
className="cell-type-datepicker"
dateFormat={dateFormat}
value={date}
onChange={dateChange}
onClose={onDatepickerClose}
/>
</>
);
};

View file

@ -7,7 +7,7 @@ export const Pagination = function Pagination({
autoCanNextPage,
autoPageCount,
autoPageOptions,
lastActivePageIndex
lastActivePageIndex,
}) {
const [pageIndex, setPageIndex] = useState(lastActivePageIndex ?? 1);
const [pageCount, setPageCount] = useState(autoPageCount);
@ -17,13 +17,12 @@ export const Pagination = function Pagination({
}, [autoPageCount]);
useEffect(() => {
if(serverSide && lastActivePageIndex > 0) {
setPageCount(lastActivePageIndex)
} else if(serverSide || lastActivePageIndex === 0) {
setPageIndex(1)
if (serverSide && lastActivePageIndex > 0) {
setPageCount(lastActivePageIndex);
} else if (serverSide || lastActivePageIndex === 0) {
setPageIndex(1);
}
}, [serverSide, lastActivePageIndex ])
}, [serverSide, lastActivePageIndex]);
function gotoPage(page) {
setPageIndex(page);
@ -42,48 +41,35 @@ export const Pagination = function Pagination({
}
return (
<div className="pagination">
{!serverSide
&& <button className="btn btn-sm btn-light mx-2" onClick={() => gotoPage(1)}>
{'<<'}
</button>
}
<button
className="btn btn-light btn-sm"
onClick={() => goToPreviousPage()}
disabled={pageIndex === 1}
>
{'<'}
</button>{' '}
<small className="p-1 mx-2">
{serverSide &&
<strong>
{pageIndex}
</strong>
}
{!serverSide &&
<strong>
{pageIndex} of {autoPageOptions.length}
</strong>
}
</small>
<button
className="btn btn-light btn-sm"
onClick={() => goToNextPage()}
disabled={!autoCanNextPage && !serverSide}
>
{'>'}
</button>{' '}
{!serverSide
&& <button
className="btn btn-light btn-sm mx-2"
onClick={() => gotoPage(pageCount)}
>
{'>>'}
</button>
}
</div>
<div className="pagination">
{!serverSide && (
<button className="btn btn-sm btn-light mx-2" onClick={() => gotoPage(1)}>
{'<<'}
</button>
)}
<button className="btn btn-light btn-sm" onClick={() => goToPreviousPage()} disabled={pageIndex === 1}>
{'<'}
</button>{' '}
<small className="p-1 mx-2">
{serverSide && <strong>{pageIndex}</strong>}
{!serverSide && (
<strong>
{pageIndex} of {autoPageOptions.length}
</strong>
)}
</small>
<button
className="btn btn-light btn-sm"
onClick={() => goToNextPage()}
disabled={!autoCanNextPage && !serverSide}
>
{'>'}
</button>{' '}
{!serverSide && (
<button className="btn btn-light btn-sm mx-2" onClick={() => gotoPage(pageCount)}>
{'>>'}
</button>
)}
</div>
);
};

View file

@ -1,20 +1,30 @@
import React, { useState } from 'react';
import React from 'react';
export const Radio = ({ options, value, onChange, readOnly }) => {
value = value === undefined ? [] : value;
options = Array.isArray(options) ? options : [];
return (
<div className="radio row">
<div>
{options.map((option) =>
<label class="form-check form-check-inline" onClick={() => { if(!readOnly) onChange(option.value); } }>
<input class="form-check-input" type="radio" checked={option.value === value} disabled={readOnly && (option.value !== value)}/>
<span class ="form-check-label">{option.name}</span>
{options.map((option, index) => (
<label
key={index}
className="form-check form-check-inline"
onClick={() => {
if (!readOnly) onChange(option.value);
}}
>
<input
className="form-check-input"
type="radio"
checked={option.value === value}
disabled={readOnly && option.value !== value}
/>
<span className="form-check-label">{option.name}</span>
</label>
)}
))}
</div>
</div>
);
};
};

View file

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useMemo, useState, useEffect } from 'react';
import {
useTable,
@ -7,7 +9,7 @@ import {
useAsyncDebounce,
usePagination,
useBlockLayout,
useResizeColumns
useResizeColumns,
} from 'react-table';
import { resolveReferences, resolveWidgetFieldValue, validateWidget } from '@/_helpers/utils';
import SelectSearch, { fuzzySearch } from 'react-select-search';
@ -17,7 +19,7 @@ import { Pagination } from './Pagination';
import { CustomSelect } from './CustomSelect';
import { Tags } from './Tags';
import { Radio } from './Radio';
import { Toggle } from './Toggle'
import { Toggle } from './Toggle';
import { Datepicker } from './Datepicker';
var _ = require('lodash');
@ -34,7 +36,7 @@ export function Table({
changeCanDrag,
onComponentOptionChanged,
onComponentOptionsChanged,
darkMode
darkMode,
}) {
const color = component.definition.styles.textColor.value;
const actions = component.definition.properties.actions || { value: [] };
@ -57,7 +59,8 @@ export function Table({
const showBulkUpdateActions = resolveWidgetFieldValue(showBulkUpdateActionsProperty, currentState) ?? true; // default is true for backward compatibility
const clientSidePaginationProperty = component.definition.properties.clientSidePagination?.value;
const clientSidePagination = resolveWidgetFieldValue(clientSidePaginationProperty, currentState) ?? !serverSidePagination; // default is true for backward compatibility
const clientSidePagination =
resolveWidgetFieldValue(clientSidePaginationProperty, currentState) ?? !serverSidePagination; // default is true for backward compatibility
const tableTypeProperty = component.definition.styles.tableType;
let tableType = tableTypeProperty ? tableTypeProperty.value : 'table-bordered';
@ -66,12 +69,15 @@ export function Table({
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
const [loadingState, setLoadingState] = useState(false);
@ -111,7 +117,7 @@ export function Table({
const newFilters = filters;
newFilters[index].value = {
...newFilters[index].value,
operation: value
operation: value,
};
setFilters(newFilters);
setAllFilters(newFilters.filter((filter) => filter.id !== ''));
@ -121,7 +127,7 @@ export function Table({
const newFilters = filters;
newFilters[index].value = {
...newFilters[index].value,
value: value
value: value,
};
setFilters(newFilters);
setAllFilters(newFilters.filter((filter) => filter.id !== ''));
@ -146,7 +152,7 @@ export function Table({
const defaultColumn = React.useMemo(
() => ({
minWidth: 60,
width: 268
width: 268,
}),
[]
);
@ -163,26 +169,24 @@ export function Table({
let newChangeset = {
...changeSet,
[index]: {
...obj
}
...obj,
},
};
obj = _.set(rowData, key, value);
let newDataUpdates = {
...dataUpdates,
[index]: { ...obj }
...dataUpdates,
[index]: { ...obj },
};
onComponentOptionsChanged(component, [
['dataUpdates', newDataUpdates],
['changeSet', newChangeset]
['changeSet', newChangeset],
]);
}
function getExportFileBlob({
columns, data
}) {
function getExportFileBlob({ columns, data }) {
const headerNames = columns.map((col) => col.exportValue);
const csvString = Papa.unparse({ fields: headerNames, data });
return new Blob([csvString], { type: 'text/csv' });
@ -190,14 +194,14 @@ export function Table({
function onPageIndexChanged(page) {
onComponentOptionChanged(component, 'pageIndex', page).then(() => {
onEvent('onPageChanged', { component, data: {} });
onEvent('onPageChanged', { component, data: {} });
});
}
function handleChangesSaved() {
Object.keys(changeSet).forEach((key) => {
tableData[key] = {
..._.merge(tableData[key], changeSet[key])
..._.merge(tableData[key], changeSet[key]),
};
});
@ -217,7 +221,9 @@ export function Table({
}
if (filterValue.operation === 'matches') {
return rows.filter((row) => row.values[columnIds[0]].toString().toLowerCase().includes(filterValue.value.toLowerCase()));
return rows.filter((row) =>
row.values[columnIds[0]].toString().toLowerCase().includes(filterValue.value.toLowerCase())
);
}
if (filterValue.operation === 'gt') {
@ -260,7 +266,13 @@ export function Table({
const columnType = column.columnType;
const columnOptions = {};
if (columnType === 'dropdown' || columnType === 'multiselect' || columnType === 'badge' || columnType === 'badges' || columnType === 'radio') {
if (
columnType === 'dropdown' ||
columnType === 'multiselect' ||
columnType === 'badge' ||
columnType === 'badges' ||
columnType === 'radio'
) {
const values = resolveReferences(column.values, currentState) || [];
const labels = resolveReferences(column.labels, currentState, []) || [];
@ -271,8 +283,8 @@ export function Table({
}
}
if (columnType === 'datepicker') {
column.isTimeChecked = column.isTimeChecked ? column.isTimeChecked : false
column.dateFormat = column.dateFormat ? column.dateFormat : 'DD/MM/YYYY'
column.isTimeChecked = column.isTimeChecked ? column.isTimeChecked : false;
column.dateFormat = column.dateFormat ? column.dateFormat : 'DD/MM/YYYY';
}
const width = columnSize || defaultColumn.width;
@ -290,34 +302,33 @@ export function Table({
const cellValue = rowChangeSet ? rowChangeSet[column.name] || cell.value : cell.value;
if (columnType === 'string' || columnType === undefined || columnType === 'default') {
const textColor = resolveReferences(column.textColor, currentState, { cellValue });
const cellStyles = {
color: textColor === undefined ? darkMode === true ? '#fff' : 'black' : textColor
}
color: textColor === undefined ? (darkMode === true ? '#fff' : 'black') : textColor,
};
if (column.isEditable) {
const validationData = validateWidget({
validationObject: {
regex: {
value: column.regex
value: column.regex,
},
minLength: {
value: column.minLength
value: column.minLength,
},
maxLength: {
value: column.maxLength
value: column.maxLength,
},
customRule: {
value: column.customRule
}
value: column.customRule,
},
},
widgetValue: cellValue,
currentState,
customResolveObjects: { cellValue }
})
customResolveObjects: { cellValue },
});
const { isValid, validationError } = validationData;
return (
@ -327,48 +338,60 @@ export function Table({
style={cellStyles}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if(e.target.defaultValue !== e.target.value) {
handleCellValueChange(cell.row.index, column.key || column.name, e.target.value, cell.row.original);
if (e.target.defaultValue !== e.target.value) {
handleCellValueChange(
cell.row.index,
column.key || column.name,
e.target.value,
cell.row.original
);
}
}
}}
onBlur={(e) => {
if(e.target.defaultValue !== e.target.value) {
handleCellValueChange(cell.row.index, column.key || column.name, e.target.value, cell.row.original);
if (e.target.defaultValue !== e.target.value) {
handleCellValueChange(
cell.row.index,
column.key || column.name,
e.target.value,
cell.row.original
);
}
}}
className={`form-control-plaintext form-control-plaintext-sm ${!isValid ? 'is-invalid' : ''}`}
defaultValue={cellValue}
/>
<div class="invalid-feedback">{validationError}</div>
<div className="invalid-feedback">{validationError}</div>
</div>
);
}
return <span style={cellStyles}>{cellValue}</span>;
} if (columnType === 'text') {
return <textarea
rows="1"
}
if (columnType === 'text') {
return (
<textarea
rows="1"
className="form-control-plaintext text-container text-muted"
readOnly={!column.isEditable}
style={{maxWidth: width, minWidth: width - 10}}
style={{ maxWidth: width, minWidth: width - 10 }}
onBlur={(e) => {
handleCellValueChange(cell.row.index, column.key || column.name, e.target.value, cell.row.original);
}}
defaultValue={cellValue}
>
</textarea>;
} if (columnType === 'dropdown') {
></textarea>
);
}
if (columnType === 'dropdown') {
const validationData = validateWidget({
validationObject: {
customRule: {
value: column.customRule
}
value: column.customRule,
},
},
widgetValue: cellValue,
currentState,
customResolveObjects: { cellValue }
})
customResolveObjects: { cellValue },
});
const { isValid, validationError } = validationData;
@ -387,7 +410,8 @@ export function Table({
<div className={`invalid-feedback ${isValid ? '' : 'd-flex'}`}>{validationError}</div>
</div>
);
} if (columnType === 'multiselect') {
}
if (columnType === 'multiselect') {
return (
<div>
<SelectSearch
@ -403,7 +427,8 @@ export function Table({
/>
</div>
);
} if (columnType === 'badge') {
}
if (columnType === 'badge') {
return (
<div>
<CustomSelect
@ -415,7 +440,8 @@ export function Table({
/>
</div>
);
} if (columnType === 'badges') {
}
if (columnType === 'badges') {
return (
<div>
<CustomSelect
@ -428,10 +454,12 @@ export function Table({
/>
</div>
);
} if (columnType === 'tags') {
}
if (columnType === 'tags') {
return (
<div>
<Tags
readOnly={!column.isEditable}
value={cellValue}
onChange={(value) => {
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
@ -439,7 +467,8 @@ export function Table({
/>
</div>
);
} if (columnType === 'radio') {
}
if (columnType === 'radio') {
return (
<div>
<Radio
@ -449,10 +478,11 @@ export function Table({
onChange={(value) => {
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
}}
/>
/>
</div>
);
} if (columnType === 'toggle') {
}
if (columnType === 'toggle') {
return (
<div>
<Toggle
@ -465,7 +495,8 @@ export function Table({
/>
</div>
);
} if (columnType === 'datepicker') {
}
if (columnType === 'datepicker') {
return (
<div>
<Datepicker
@ -481,7 +512,7 @@ export function Table({
);
}
return cellValue || '';
}
},
};
});
@ -494,18 +525,19 @@ export function Table({
tableData = tableData || [];
const leftActions = () => actions.value.filter(action => action.position === 'left')
const rightActions = () => actions.value.filter(action => [undefined, 'right'].includes(action.position))
const leftActions = () => actions.value.filter((action) => action.position === 'left');
const rightActions = () => actions.value.filter((action) => [undefined, 'right'].includes(action.position));
const leftActionsCellData = leftActions().length > 0
? [
{
id: 'leftActions',
Header: 'Actions',
accessor: 'edit',
width: columnSizes.leftActions || defaultColumn.width,
Cell: (cell) => {
return leftActions().map((action) => (
const leftActionsCellData =
leftActions().length > 0
? [
{
id: 'leftActions',
Header: 'Actions',
accessor: 'edit',
width: columnSizes.leftActions || defaultColumn.width,
Cell: (cell) => {
return leftActions().map((action) => (
<button
key={action.name}
className="btn btn-sm m-1 btn-light"
@ -517,21 +549,22 @@ export function Table({
>
{action.buttonText}
</button>
));
}
}
]
: [];
));
},
},
]
: [];
const rightActionsCellData = rightActions().length > 0
? [
{
id: 'rightActions',
Header: 'Actions',
accessor: 'edit',
width: columnSizes.rightActions || defaultColumn.width,
Cell: (cell) => {
return rightActions().map((action) => (
const rightActionsCellData =
rightActions().length > 0
? [
{
id: 'rightActions',
Header: 'Actions',
accessor: 'edit',
width: columnSizes.rightActions || defaultColumn.width,
Cell: (cell) => {
return rightActions().map((action) => (
<button
key={action.name}
className="btn btn-sm m-1 btn-light"
@ -543,23 +576,23 @@ export function Table({
>
{action.buttonText}
</button>
));
}
}
]
: [];
));
},
},
]
: [];
const optionsData = columnData.map(column => column.columnOptions?.selectOptions);
const optionsData = columnData.map((column) => column.columnOptions?.selectOptions);
const columns = useMemo(
() => [...leftActionsCellData, ...columnData, ...rightActionsCellData],
[JSON.stringify(columnData),
[
JSON.stringify(columnData),
leftActionsCellData.length,
rightActionsCellData.length,
componentState.changeSet,
JSON.stringify(optionsData),
JSON.stringify(component.definition.properties.columns)
JSON.stringify(component.definition.properties.columns),
] // Hack: need to fix
);
@ -567,7 +600,7 @@ export function Table({
const computedStyles = {
color,
width: `${width}px`
width: `${width}px`,
};
const {
@ -590,16 +623,17 @@ export function Table({
preGlobalFilteredRows,
setGlobalFilter,
state: { pageIndex, pageSize },
exportData
exportData,
} = useTable(
{
autoResetPage: false,
columns,
data,
defaultColumn,
initialState: { pageIndex: 0, pageSize: -1},
pageCount: -1,
manualPagination: false,
getExportFileBlob
initialState: { pageIndex: 0, pageSize: -1 },
pageCount: -1,
manualPagination: false,
getExportFileBlob,
},
useFilters,
useGlobalFilter,
@ -610,32 +644,28 @@ export function Table({
useExportData
);
React.useEffect(() => {
if(serverSidePagination || !clientSidePagination) {
setPageSize(-1)
}
if(!serverSidePagination && clientSidePagination) {
setPageSize(10)
if (serverSidePagination || !clientSidePagination) {
setPageSize(-1);
}
},[clientSidePagination, serverSidePagination])
if (!serverSidePagination && clientSidePagination) {
setPageSize(10);
}
}, [clientSidePagination, serverSidePagination]);
useEffect(() => {
const pageData = page.map(row => row.original);
const currentData = rows.map(row => row.original);;
const pageData = page.map((row) => row.original);
const currentData = rows.map((row) => row.original);
onComponentOptionsChanged(component, [
['currentPageData', pageData],
['currentData', currentData]
['currentData', currentData],
]);
}, [tableData.length, componentState.changeSet]);
useEffect(() => {
if (!state.columnResizing.isResizingColumn) {
changeCanDrag(true);
paramUpdated(id, 'columnSizes', { ...columnSizes, ...state.columnResizing.columnWidths});
paramUpdated(id, 'columnSizes', { ...columnSizes, ...state.columnResizing.columnWidths });
} else {
changeCanDrag(false);
}
@ -649,16 +679,15 @@ export function Table({
}, 200);
const handleSearchTextChange = (text) => {
setValue(text);
onChange(text);
onComponentOptionChanged(component, 'searchText', text).then(() => {
if(serverSideSearch === true ) {
if (serverSideSearch === true) {
onEvent('onSearch', { component, data: {} });
}
});
}
};
return (
<div className="ms-2 d-inline-block">
@ -667,18 +696,16 @@ export function Table({
className="global-search-field"
defaultValue={value || ''}
onBlur={(e) => {
handleSearchTextChange(e.target.value)
handleSearchTextChange(e.target.value);
}}
onKeyDown={(e) => {
if(e.key === 'Enter') {
handleSearchTextChange(e.target.value)
if (e.key === 'Enter') {
handleSearchTextChange(e.target.value);
}
}
}
}}
placeholder={`${count} records`}
style={{
border: '0'
border: '0',
}}
/>
</div>
@ -689,26 +716,32 @@ export function Table({
<div
data-disabled={parsedDisabledState}
className="card jet-table"
style={{ width: `${width}px`, height: `${height}px`, display:parsedWidgetVisibility ? '' : 'none' }}
onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}
style={{ width: `${width}px`, height: `${height}px`, display: parsedWidgetVisibility ? '' : 'none' }}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
{/* Show top bar unless search box is disabled and server pagination is enabled */}
{displaySearchBox &&
{displaySearchBox && (
<div className="card-body border-bottom py-3 jet-data-table-header">
<div className="d-flex">
{displaySearchBox && <div className="ms-auto text-muted">
<GlobalFilter />
</div>}
{displaySearchBox && (
<div className="ms-auto text-muted">
<GlobalFilter />
</div>
)}
</div>
</div>
}
)}
<div className="table-responsive jet-data-table">
<table {...getTableProps()} className={`table table-vcenter table-nowrap ${tableType}`} style={computedStyles}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()} tabIndex="0" className="tr">
{headerGroup.headers.map((column) => (
{headerGroups.map((headerGroup, index) => (
<tr key={index} {...headerGroup.getHeaderGroupProps()} tabIndex="0" className="tr">
{headerGroup.headers.map((column, index) => (
<th
key={index}
{...column.getHeaderProps(column.getSortByToggleProps())}
className={column.isSorted ? (column.isSortedDesc ? 'sort-desc th' : 'sort-asc th') : 'th'}
>
@ -724,17 +757,20 @@ export function Table({
))}
</thead>
{!loadingState && page.length === 0 &&
<center className="w-100"><div className="py-5"> no data </div></center>
}
{!loadingState && page.length === 0 && (
<center className="w-100">
<div className="py-5"> no data </div>
</center>
)}
{!loadingState && (
<tbody {...getTableBodyProps()}>
{console.log('page', page)}
{page.map((row) => {
{page.map((row, index) => {
prepareRow(row);
return (
<tr
key={index}
className="table-row"
{...row.getRowProps()}
onClick={(e) => {
@ -742,20 +778,26 @@ export function Table({
onEvent('onRowClicked', { component, data: row.original });
}}
>
{row.cells.map((cell) => {
{row.cells.map((cell, index) => {
let cellProps = cell.getCellProps();
if (componentState.changeSet) {
if (componentState.changeSet[cell.row.index]) {
const currentColumn = columnData.find((column) => column.id === cell.column.id);
const currentColumn = columnData.find(column => column.id === cell.column.id);
if (_.get(componentState.changeSet[cell.row.index], currentColumn?.accessor, undefined) !== undefined) {
if (
_.get(componentState.changeSet[cell.row.index], currentColumn?.accessor, undefined) !==
undefined
) {
console.log('componentState.changeSet', componentState.changeSet);
cellProps.style.backgroundColor = '#ffffde';
}
}
}
return <td {...cellProps}>{cell.render('Cell')}</td>;
return (
<td key={index} {...cellProps}>
{cell.render('Cell')}
</td>
);
})}
</tr>
);
@ -771,30 +813,35 @@ export function Table({
</div>
)}
</div>
{(clientSidePagination || serverSidePagination || Object.keys(componentState.changeSet || {}).length > 0 || showFilterButton || showDownloadButton) &&
{(clientSidePagination ||
serverSidePagination ||
Object.keys(componentState.changeSet || {}).length > 0 ||
showFilterButton ||
showDownloadButton) && (
<div className="card-footer d-flex align-items-center jet-table-footer">
<div className="table-footer row">
<div className="col">
{(clientSidePagination || serverSidePagination) &&
{(clientSidePagination || serverSidePagination) && (
<Pagination
lastActivePageIndex={currentState.components[component.name]?.pageIndex ?? 1 }
serverSide={serverSidePagination}
autoGotoPage={gotoPage}
autoCanNextPage={canNextPage}
autoPageCount={pageCount}
autoPageOptions={pageOptions}
onPageIndexChanged={onPageIndexChanged}
lastActivePageIndex={currentState.components[component.name]?.pageIndex ?? 1}
serverSide={serverSidePagination}
autoGotoPage={gotoPage}
autoCanNextPage={canNextPage}
autoPageCount={pageCount}
autoPageOptions={pageOptions}
onPageIndexChanged={onPageIndexChanged}
/>
}
)}
</div>
{(showBulkUpdateActions && Object.keys(componentState.changeSet || {}).length > 0) && (
{showBulkUpdateActions && Object.keys(componentState.changeSet || {}).length > 0 && (
<div className="col">
<button
className={`btn btn-primary btn-sm ${componentState.isSavingChanges ? 'btn-loading' : ''}`}
onClick={() => onEvent('onBulkUpdate', { component }).then(() => {
handleChangesSaved();
})
onClick={() =>
onEvent('onBulkUpdate', { component }).then(() => {
handleChangesSaved();
})
}
>
Save Changes
@ -806,15 +853,15 @@ export function Table({
)}
<div className="col-auto">
{showFilterButton &&
{showFilterButton && (
<span data-tip="Filter data" className="btn btn-light btn-sm p-1 mx-2" onClick={() => showFilters()}>
<img src="/assets/images/icons/filter.svg" width="13" height="13" />
{filters.length > 0 &&
<a className="badge bg-azure" style={{width: '4px', height: '4px', marginTop: '5px'}}></a>
}
{filters.length > 0 && (
<a className="badge bg-azure" style={{ width: '4px', height: '4px', marginTop: '5px' }}></a>
)}
</span>
}
{showDownloadButton &&
)}
{showDownloadButton && (
<span
data-tip="Download as CSV"
className="btn btn-light btn-sm p-1"
@ -822,11 +869,11 @@ export function Table({
>
<img src="/assets/images/icons/download.svg" width="13" height="13" />
</span>
}
)}
</div>
</div>
</div>
}
)}
{isFiltersVisible && (
<div className="table-filters card">
<div className="card-header row">
@ -868,7 +915,7 @@ export function Table({
{ name: 'greater than', value: 'gt' },
{ name: 'less than', value: 'lt' },
{ name: 'greater than or equals', value: 'gte' },
{ name: 'less than or equals', value: 'lte' }
{ name: 'less than or equals', value: 'lte' },
]}
value={filter.value.operation}
search={true}

View file

@ -1,62 +1,67 @@
import React, { useState } from 'react';
export const Tags = ({ value, onChange }) => {
export const Tags = ({ value, onChange, readOnly }) => {
value = value || [];
value = value || [];
const [showForm, setShowForm] = useState(false);
const [ showForm, setShowForm ] = useState(false);
function addTag(text) {
if(text !== '') {
value.push(text);
onChange(value);
} else {
setShowForm(false);
}
function addTag(text) {
if (text !== '') {
value.push(text);
onChange(value);
} else {
setShowForm(false);
}
}
function removeTag(text) {
const newValue = value.filter(tag => tag !== text);
onChange(newValue);
setShowForm(false);
}
function removeTag(text) {
const newValue = value.filter((tag) => tag !== text);
onChange(newValue);
setShowForm(false);
}
function handleFormKeyDown(e) {
if(e.key === 'Enter') {
addTag(e.target.value)
}
}
function renderTag(text) {
return <span className="col-auto badge bg-blue-lt p-2 mx-1 tag mb-2">
{text}
<span className="badge badge-pill bg-red-lt remove-tag-button" onClick={() => removeTag(text)}>
x
</span>
</span>;
function handleFormKeyDown(e) {
if (e.key === 'Enter') {
addTag(e.target.value);
}
}
function renderTag(text) {
return (
<div className="tags row">
{value.map((item) => {
return renderTag(item)
})}
{!showForm &&
<span className="col-auto badge bg-green-lt mx-1 add-tag-button" onClick={() => setShowForm(true)}>{'+'}</span>
}
{showForm &&
<span className="col-auto badge bg-green-lt mx-1">
<input
type="text"
autoFocus
className="form-control-plaintext"
onBlur={(e) => addTag(e.target.value)}
onKeyDown={handleFormKeyDown}
/>
</span>
}
</div>
<span className="col-auto badge bg-blue-lt p-2 mx-1 tag mb-2">
{text}
{!readOnly && (
<span className="badge badge-pill bg-red-lt remove-tag-button" onClick={() => removeTag(text)}>
x
</span>
)}
</span>
);
};
}
return (
<div className="tags row">
{value.map((item) => {
return renderTag(item);
})}
{!showForm && !readOnly && (
<span className="col-auto badge bg-green-lt mx-1 add-tag-button" onClick={() => setShowForm(true)}>
{'+'}
</span>
)}
{showForm && (
<span className="col-auto badge bg-green-lt mx-1">
<input
type="text"
autoFocus
className="form-control-plaintext"
onBlur={(e) => addTag(e.target.value)}
onKeyDown={handleFormKeyDown}
/>
</span>
)}
</div>
);
};

View file

@ -1,24 +1,26 @@
import React, { useState } from 'react';
export const Toggle = ({readOnly, value, onChange, activeColor, options }) => {
const [on, setOn] = useState(() => value)
export const Toggle = ({ readOnly, value, onChange, activeColor }) => {
const [on, setOn] = useState(() => value);
const toggle = () => {
setOn((prev) => !prev)
onChange(!on)
}
setOn((prev) => !prev);
onChange(!on);
};
return (
<div className="radio row g-0">
<label className="form-check form-switch form-check-inline">
<input
className="form-check-input"
type="checkbox"
checked={on}
style={ on ? { backgroundColor: activeColor} : {}}
onClick={() => {if(!readOnly) toggle()}}
/>
</label>
<label className="form-check form-switch form-check-inline">
<input
className="form-check-input"
type="checkbox"
checked={on}
style={on ? { backgroundColor: activeColor } : {}}
onClick={() => {
if (!readOnly) toggle();
}}
/>
</label>
</div>
);
};
};

View file

@ -1,17 +1,15 @@
import React, { useState, useEffect } from 'react';
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
import DOMPurify from 'dompurify';
import Skeleton from 'react-loading-skeleton';
export const Text = function Text({
id, width, height, component, onComponentClick, currentState
}) {
export const Text = function Text({ id, width, height, component, onComponentClick, currentState }) {
const text = component.definition.properties.text.value;
const color = component.definition.styles.textColor.value;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const [loadingState, setLoadingState] = useState(false);
@ -21,6 +19,7 @@ export const Text = function Text({
const newState = resolveReferences(loadingStateProperty.value, currentState, false);
setLoadingState(newState);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentState]);
let data = text;
@ -37,21 +36,31 @@ export const Text = function Text({
}
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
const computedStyles = {
color,
width,
height,
display: parsedWidgetVisibility ? 'flex' : 'none',
alignItems: 'center'
alignItems: 'center',
};
return (
<div data-disabled={parsedDisabledState} className="text-widget" style={computedStyles} onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}>
<div
data-disabled={parsedDisabledState}
className="text-widget"
style={computedStyles}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
>
{!loadingState && <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(data) }} />}
{loadingState === true && (
<div>

View file

@ -8,9 +8,8 @@ export const TextArea = function TextArea({
component,
onComponentClick,
currentState,
onComponentOptionChanged
onComponentOptionChanged,
}) {
const value = component.definition.properties.value ? component.definition.properties.value.value : '';
const [text, setText] = useState(value);
@ -23,24 +22,31 @@ export const TextArea = function TextArea({
useEffect(() => {
setText(newText);
onComponentOptionChanged(component, 'value', newText);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [newText]);
const placeholder = component.definition.properties.placeholder.value;
const widgetVisibility = component.definition.styles?.visibility?.value ?? true;
const disabledState = component.definition.styles?.disabledState?.value ?? false;
const parsedDisabledState = typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
const parsedDisabledState =
typeof disabledState !== 'boolean' ? resolveWidgetFieldValue(disabledState, currentState) : disabledState;
let parsedWidgetVisibility = widgetVisibility;
try {
parsedWidgetVisibility = resolveReferences(parsedWidgetVisibility, currentState, []);
} catch (err) { console.log(err); }
} catch (err) {
console.log(err);
}
return (
<textarea
disabled={parsedDisabledState}
onClick={event => {event.stopPropagation(); onComponentClick(id, component)}}
onClick={(event) => {
event.stopPropagation();
onComponentClick(id, component);
}}
onChange={(e) => {
setText(e.target.value);
onComponentOptionChanged(component, 'value', e.target.value);
@ -48,7 +54,7 @@ export const TextArea = function TextArea({
type="text"
className="form-control"
placeholder={placeholder}
style={{ width, height, display:parsedWidgetVisibility ? '' : 'none' }}
style={{ width, height, display: parsedWidgetVisibility ? '' : 'none' }}
value={text}
></textarea>
);

View file

@ -32,6 +32,7 @@ export const TextInput = function TextInput({
useEffect(() => {
setText(newText);
onComponentOptionChanged(component, 'value', newText);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [newText]);
const validationData = validateWidget({

View file

@ -35,15 +35,19 @@ export const componentTypes = [
},
styles: {
textColor: { type: 'color', displayName: 'Text Color' },
tableType: { type: 'select', displayName: 'Table type', options: [
{ name: 'Bordered', value: '' },
{ name: 'Borderless', value: 'table-borderless' },
{ name: 'Classic', value: 'table-classic' },
{ name: 'Striped', value: 'table-striped' },
{ name: 'Striped & bordered', value: 'table-striped table-bordered' }
] },
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
tableType: {
type: 'select',
displayName: 'Table type',
options: [
{ name: 'Bordered', value: '' },
{ name: 'Borderless', value: 'table-borderless' },
{ name: 'Classic', value: 'table-classic' },
{ name: 'Striped', value: 'table-striped' },
{ name: 'Striped & bordered', value: 'table-striped table-bordered' },
],
},
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
selectedRow: {},
@ -82,8 +86,8 @@ export const componentTypes = [
events: [],
styles: {
textColor: { value: '' },
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -110,8 +114,8 @@ export const componentTypes = [
styles: {
backgroundColor: { type: 'color', displayName: 'Background color' },
textColor: { type: 'color', displayName: 'Text color' },
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {},
definition: {
@ -128,8 +132,8 @@ export const componentTypes = [
styles: {
backgroundColor: { value: '#3c92dc' },
textColor: { value: '#fff' },
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -164,8 +168,8 @@ export const componentTypes = [
},
events: {},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
show: null,
@ -183,16 +187,16 @@ export const componentTypes = [
type: { value: `line` },
data: {
value: `[
{ "x": 100, "y": "Jan"},
{ "x": 80, "y": "Feb"},
{ "x": 40, "y": "Mar"}
{ "x": "Jan", "y": 100},
{ "x": "Feb", "y": 80},
{ "x": "Mar", "y": 40}
]`,
},
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -223,7 +227,7 @@ export const componentTypes = [
},
events: {},
styles: {
disabledState: {type: 'code', displayName: 'Disable'}
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
show: null,
@ -239,7 +243,7 @@ export const componentTypes = [
},
events: [],
styles: {
disabledState: {value: '{{false}}'}
disabledState: { value: '{{false}}' },
},
},
},
@ -258,28 +262,28 @@ export const componentTypes = [
},
properties: {
value: { type: 'code', displayName: 'Default value' },
placeholder: { type: 'code', displayName: 'Placeholder' }
placeholder: { type: 'code', displayName: 'Placeholder' },
},
validation: {
regex: { type: 'code', displayName: 'Regex' },
minLength: { type: 'code', displayName: 'Min length' },
maxLength: { type: 'code', displayName: 'Max length' },
customRule: { type: 'code', displayName: 'Custom validation' }
customRule: { type: 'code', displayName: 'Custom validation' },
},
events: {},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
value: '',
value: '',
},
definition: {
validation: {
regex: { value: '' },
minLength: { value: null },
maxLength: { value: null },
customRule: { value: null }
customRule: { value: null },
},
others: {
showOnDesktop: { value: true },
@ -287,12 +291,12 @@ export const componentTypes = [
},
properties: {
value: { value: '' },
placeholder: { value: 'Placeholder text' }
placeholder: { value: 'Placeholder text' },
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -311,12 +315,12 @@ export const componentTypes = [
},
properties: {
value: { type: 'code', displayName: 'Default value' },
placeholder: { type: 'code', displayName: 'Placeholder' }
placeholder: { type: 'code', displayName: 'Placeholder' },
},
events: {},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
value: 0,
@ -328,12 +332,12 @@ export const componentTypes = [
},
properties: {
value: { value: '' },
placeholder: { value: '0' }
placeholder: { value: '0' },
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -347,24 +351,25 @@ export const componentTypes = [
height: 30,
},
validation: {
customRule: { type: 'code', displayName: 'Custom validation' }
customRule: { type: 'code', displayName: 'Custom validation' },
},
others: {
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
},
properties: {
defaultValue: { type: 'code', displayName: 'Default value' },
format: { type: 'code', displayName: 'Format' },
enableTime: { type: 'code', displayName: 'Enable time selection?' },
enableDate: { type: 'code', displayName: 'Enable date selection?' },
},
events: {},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
value: {},
value: '',
},
definition: {
others: {
@ -372,17 +377,18 @@ export const componentTypes = [
showOnMobile: { value: false },
},
validation: {
customRule: { value: null }
customRule: { value: null },
},
properties: {
defaultValue: { value: '' },
format: { value: 'DD/MM/YYYY' },
enableTime: { value: '{{false}}' },
enableDate: { value: '{{true}}' },
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -408,8 +414,8 @@ export const componentTypes = [
},
styles: {
textColor: { type: 'color', displayName: 'Text Color' },
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {},
definition: {
@ -423,8 +429,8 @@ export const componentTypes = [
events: [],
styles: {
textColor: { value: '#000' },
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -452,8 +458,8 @@ export const componentTypes = [
},
styles: {
textColor: { type: 'color', displayName: 'Text Color' },
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {},
definition: {
@ -471,8 +477,8 @@ export const componentTypes = [
events: [],
styles: {
textColor: { value: '#000' },
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -497,8 +503,8 @@ export const componentTypes = [
},
styles: {
textColor: { type: 'color', displayName: 'Text Color' },
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {},
definition: {
@ -512,8 +518,8 @@ export const componentTypes = [
events: [],
styles: {
textColor: { value: '#000' },
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -536,8 +542,8 @@ export const componentTypes = [
},
events: {},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
value: {},
@ -553,8 +559,8 @@ export const componentTypes = [
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -576,8 +582,8 @@ export const componentTypes = [
},
events: {},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
endDate: {},
@ -593,8 +599,8 @@ export const componentTypes = [
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -618,8 +624,8 @@ export const componentTypes = [
events: [],
styles: {
textColor: { type: 'color', displayName: 'Text Color' },
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {},
definition: {
@ -635,8 +641,8 @@ export const componentTypes = [
events: [],
styles: {
textColor: { value: '#000' },
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -660,8 +666,8 @@ export const componentTypes = [
onClick: { displayName: 'On click' },
},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {},
definition: {
@ -675,8 +681,8 @@ export const componentTypes = [
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -693,13 +699,12 @@ export const componentTypes = [
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop? ' },
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
},
properties: {
},
properties: {},
events: {},
styles: {
backgroundColor: { type: 'color' },
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {},
definition: {
@ -713,8 +718,8 @@ export const componentTypes = [
events: [],
styles: {
backgroundColor: { value: '#fff' },
visibility: {value: '{{true}}'},
disabledState: {value:'{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -732,7 +737,7 @@ export const componentTypes = [
showOnMobile: { type: 'toggle', displayName: 'Show on mobile?' },
},
validation: {
customRule: { type: 'code', displayName: 'Custom validation' }
customRule: { type: 'code', displayName: 'Custom validation' },
},
properties: {
label: { type: 'code', displayName: 'Label' },
@ -744,8 +749,8 @@ export const componentTypes = [
onSelect: { displayName: 'On select' },
},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
value: null,
@ -756,7 +761,7 @@ export const componentTypes = [
showOnMobile: { value: false },
},
validation: {
customRule: { value: null }
customRule: { value: null },
},
properties: {
label: { value: 'Select' },
@ -767,8 +772,8 @@ export const componentTypes = [
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -795,8 +800,8 @@ export const componentTypes = [
onSelect: { displayName: 'On select' },
},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
values: {},
@ -815,8 +820,8 @@ export const componentTypes = [
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -838,8 +843,8 @@ export const componentTypes = [
},
events: {},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
value: {},
@ -854,8 +859,8 @@ export const componentTypes = [
},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -901,8 +906,8 @@ export const componentTypes = [
onMarkerClick: { displayName: 'On marker click' },
},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
center: {},
@ -923,8 +928,8 @@ export const componentTypes = [
addNewMarkers: { value: '{{false}}' },
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -946,8 +951,8 @@ export const componentTypes = [
onDetect: { displayName: 'On detect' },
},
styles: {
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
lastDetectedValue: '',
@ -960,8 +965,8 @@ export const componentTypes = [
properties: {},
events: [],
styles: {
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},
@ -990,8 +995,8 @@ export const componentTypes = [
},
styles: {
textColor: { type: 'color', displayName: 'Star Color' },
visibility: {type: 'code', displayName: 'Visibility'},
disabledState: {type: 'code', displayName: 'Disable'}
visibility: { type: 'code', displayName: 'Visibility' },
disabledState: { type: 'code', displayName: 'Disable' },
},
exposedVariables: {
value: 0,
@ -1012,8 +1017,8 @@ export const componentTypes = [
events: [],
styles: {
textColor: { value: '#ffb400' },
visibility: {value: '{{true}}'},
disabledState: {value: '{{false}}'}
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
},
},
},

View file

@ -1,39 +1,35 @@
import React from 'react';
export const ConfigHandle = function ConfigHandle({
id,
component,
configHandleClicked,
dragRef,
removeComponent
}) {
return <div className="config-handle" ref={dragRef}>
<span
style={{cursor: 'move'}}
onClick={(e) => { e.preventDefault(); e.stopPropagation(); configHandleClicked(id, component) }}
className="badge badge bg-azure-lt"
role="button"
>
<img
style={{cursor: 'pointer'}}
src="/assets/images/icons/menu.svg"
width="8"
height="8"
style={{marginRight: '5px'}}
/>
{component.name}
</span>
<img
style={{cursor: 'pointer'}}
src="/assets/images/icons/trash.svg"
width="12"
role="button"
className="mx-2"
height="12"
onClick={() => removeComponent({id})}
style={{marginRight: '5px'}}
export const ConfigHandle = function ConfigHandle({ id, component, configHandleClicked, dragRef, removeComponent }) {
return (
<div className="config-handle" ref={dragRef}>
<span
style={{ cursor: 'move' }}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
configHandleClicked(id, component);
}}
className="badge badge bg-azure-lt"
role="button"
>
<img
style={{ cursor: 'pointer', marginRight: '5px' }}
src="/assets/images/icons/menu.svg"
width="8"
height="8"
/>
{component.name}
</span>
<img
style={{ cursor: 'pointer', marginRight: '5px' }}
src="/assets/images/icons/trash.svg"
width="12"
role="button"
className="mx-2"
height="12"
onClick={() => removeComponent({ id })}
/>
</div>
}
);
};

View file

@ -7,10 +7,10 @@ import update from 'immutability-helper';
import { componentTypes } from './Components/components';
import { computeComponentName } from '@/_helpers/utils';
function uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
);
}
export const Container = ({
@ -31,13 +31,12 @@ export const Container = ({
deviceWindowWidth,
scaleValue,
selectedComponent,
darkMode
darkMode,
}) => {
const styles = {
width: currentLayout === 'mobile' ? deviceWindowWidth : 1292,
height: 2400,
position: 'absolute'
position: 'absolute',
};
const components = appDefinition.components;
@ -55,8 +54,8 @@ export const Container = ({
setBoxes(
update(boxes, {
[id]: {
$merge: { layouts }
}
$merge: { layouts },
},
})
);
console.log('new boxes - 1', boxes);
@ -67,17 +66,18 @@ export const Container = ({
useEffect(() => {
console.log('new boxes - 2', boxes);
appDefinitionChanged({ ...appDefinition, components: boxes });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [boxes]);
const { draggingState } = useDragLayer((monitor) => {
if(monitor.isDragging()) {
if(!monitor.getItem().parent) {
return { draggingState: true }
if (monitor.isDragging()) {
if (!monitor.getItem().parent) {
return { draggingState: true };
} else {
return { draggingState: false }
return { draggingState: false };
}
} else {
return { draggingState: false }
return { draggingState: false };
}
});
@ -89,8 +89,7 @@ export const Container = ({
() => ({
accept: ItemTypes.BOX,
drop(item, monitor) {
if(item.parent) {
if (item.parent) {
return;
}
@ -112,7 +111,7 @@ export const Container = ({
let deltaX = 0;
let deltaY = 0;
if(delta) {
if (delta) {
deltaX = delta.x;
deltaY = delta.y;
}
@ -135,13 +134,12 @@ export const Container = ({
...boxes[id]['layouts'][item.currentLayout],
top: top,
left: left,
}
}
}
},
},
},
};
setBoxes(newBoxes);
} else {
// This is a new component
componentMeta = componentTypes.find((component) => component.component === item.component.component);
@ -153,8 +151,8 @@ export const Container = ({
const offsetFromLeftOfWindow = canvasBoundingRect.left;
const currentOffset = monitor.getSourceClientOffset();
left = Math.round(currentOffset.x + (currentOffset.x * (1 - zoomLevel)) - offsetFromLeftOfWindow);
top = Math.round(currentOffset.y + (currentOffset.y * (1 - zoomLevel)) - offsetFromTopOfWindow);
left = Math.round(currentOffset.x + currentOffset.x * (1 - zoomLevel) - offsetFromLeftOfWindow);
top = Math.round(currentOffset.y + currentOffset.y * (1 - zoomLevel) - offsetFromTopOfWindow);
id = uuidv4();
@ -162,7 +160,7 @@ export const Container = ({
[left, top] = doSnapToGrid(left, top);
}
if(item.currentLayout === 'mobile') {
if (item.currentLayout === 'mobile') {
componentData.definition.others.showOnDesktop.value = false;
componentData.definition.others.showOnMobile.value = true;
}
@ -177,14 +175,14 @@ export const Container = ({
left: left,
width: componentMeta.defaultSize.width,
height: componentMeta.defaultSize.height,
}
}
}
},
},
},
});
}
return undefined;
}
},
}),
[moveBox]
);
@ -199,10 +197,10 @@ export const Container = ({
top: 100,
left: 0,
width: 445,
height: 500
height: 500,
};
let { left, top, width, height } = boxes[id]['layouts'][currentLayout] || defaultData;
let { left, top, width, height } = boxes[id]['layouts'][currentLayout] || defaultData;
top = y;
left = x;
@ -220,10 +218,13 @@ export const Container = ({
...boxes[id]['layouts'],
[currentLayout]: {
...boxes[id]['layouts'][currentLayout],
width, height, top, left
}
}
}
width,
height,
top,
left,
},
},
},
};
setBoxes(newBoxes);
@ -241,12 +242,12 @@ export const Container = ({
...boxes[id].component.definition,
properties: {
...boxes[id].component.definition.properties,
[param]: value
}
}
}
}
}
[param]: value,
},
},
},
},
},
})
);
}
@ -255,60 +256,63 @@ export const Container = ({
return (
<div ref={drop} style={styles} className={`real-canvas ${isDragging || isResizing ? 'show-grid' : ''}`}>
{Object.keys(boxes).map((key) => {
const box = boxes[key];
const canShowInCurrentLayout = box.component.definition.others[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value;
const canShowInCurrentLayout =
box.component.definition.others[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value;
if(!box.parent && canShowInCurrentLayout) {
return <DraggableBox
onComponentClick={onComponentClick}
onEvent={onEvent}
onComponentOptionChanged={onComponentOptionChanged}
onComponentOptionsChanged={onComponentOptionsChanged}
key={key}
currentState={currentState}
onResizeStop={onResizeStop}
paramUpdated={paramUpdated}
id={key}
{...boxes[key]}
mode={mode}
resizingStatusChanged={(status) => setIsResizing(status)}
inCanvas={true}
zoomLevel={zoomLevel}
configHandleClicked={configHandleClicked}
removeComponent={removeComponent}
currentLayout={currentLayout}
scaleValue={scaleValue}
deviceWindowWidth={deviceWindowWidth}
isSelectedComponent={selectedComponent? selectedComponent.id === key : false}
darkMode={darkMode}
containerProps={{
mode,
snapToGrid,
onComponentClick,
onEvent,
appDefinition,
appDefinitionChanged,
currentState,
onComponentOptionChanged,
onComponentOptionsChanged,
appLoading,
zoomLevel,
configHandleClicked,
removeComponent,
currentLayout,
scaleValue,
deviceWindowWidth,
selectedComponent,
darkMode
}}
/>
if (!box.parent && canShowInCurrentLayout) {
return (
<DraggableBox
onComponentClick={onComponentClick}
onEvent={onEvent}
onComponentOptionChanged={onComponentOptionChanged}
onComponentOptionsChanged={onComponentOptionsChanged}
key={key}
currentState={currentState}
onResizeStop={onResizeStop}
paramUpdated={paramUpdated}
id={key}
{...boxes[key]}
mode={mode}
resizingStatusChanged={(status) => setIsResizing(status)}
inCanvas={true}
zoomLevel={zoomLevel}
configHandleClicked={configHandleClicked}
removeComponent={removeComponent}
currentLayout={currentLayout}
scaleValue={scaleValue}
deviceWindowWidth={deviceWindowWidth}
isSelectedComponent={selectedComponent ? selectedComponent.id === key : false}
darkMode={darkMode}
containerProps={{
mode,
snapToGrid,
onComponentClick,
onEvent,
appDefinition,
appDefinitionChanged,
currentState,
onComponentOptionChanged,
onComponentOptionsChanged,
appLoading,
zoomLevel,
configHandleClicked,
removeComponent,
currentLayout,
scaleValue,
deviceWindowWidth,
selectedComponent,
darkMode,
}}
/>
);
}
}
)}
})}
{Object.keys(boxes).length === 0 && !appLoading && !isDragging && (
<div className="mx-auto w-50 p-5 bg-light no-components-box" style={{ marginTop: '10%'}}>
<center className="text-muted">You haven&apos;t added any components yet. Drag components from the right sidebar and drop here.</center>
<div className="mx-auto w-50 p-5 bg-light no-components-box" style={{ marginTop: '10%' }}>
<center className="text-muted">
You haven&apos;t added any components yet. Drag components from the right sidebar and drop here.
</center>
</div>
)}
{appLoading && (

View file

@ -10,13 +10,13 @@ const layerStyles = {
left: 0,
top: 0,
width: '100%',
height: '100%'
height: '100%',
};
function getItemStyles(delta, item, initialOffset, currentOffset, currentLayout) {
if (!initialOffset || !currentOffset) {
return {
display: 'none'
display: 'none',
};
}
let { x, y } = currentOffset;
@ -28,18 +28,19 @@ function getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)
const realCanvasDelta = realCanvasBoundingRect.x - canvasContainerBoundingRect.x;
if (id) { // Dragging within the canvas
if (id) {
// Dragging within the canvas
x = Math.round(item.layouts[currentLayout].left + delta.x);
y = Math.round(item.layouts[currentLayout].top + delta.y);
} else { // New component being dragged from components sidebar
} else {
// New component being dragged from components sidebar
const offsetFromTopOfWindow = realCanvasBoundingRect.top;
const offsetFromLeftOfWindow = realCanvasBoundingRect.left;
const zoomLevel = item.zoomLevel;
x = Math.round(currentOffset.x + (currentOffset.x * (1 - zoomLevel)) - offsetFromLeftOfWindow);
y = Math.round(currentOffset.y + (currentOffset.y * (1 - zoomLevel)) - offsetFromTopOfWindow);
x = Math.round(currentOffset.x + currentOffset.x * (1 - zoomLevel) - offsetFromLeftOfWindow);
y = Math.round(currentOffset.y + currentOffset.y * (1 - zoomLevel) - offsetFromTopOfWindow);
}
[x, y] = snapToGrid(x, y);
@ -49,24 +50,22 @@ function getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)
const transform = `translate(${x}px, ${y}px)`;
return {
transform,
WebkitTransform: transform
WebkitTransform: transform,
};
}
export const CustomDragLayer = ({ currentLayout }) => {
const {
itemType, isDragging, item, initialOffset, currentOffset, delta
} = useDragLayer((monitor) => ({
const { itemType, isDragging, item, initialOffset, currentOffset, delta } = useDragLayer((monitor) => ({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialOffset: monitor.getInitialSourceClientOffset(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
delta: monitor.getDifferenceFromInitialOffset()
delta: monitor.getDifferenceFromInitialOffset(),
}));
function renderItem() {
switch (itemType) {
case ItemTypes.BOX:
return <BoxDragPreview item={item} currentLayout={currentLayout}/>;
return <BoxDragPreview item={item} currentLayout={currentLayout} />;
default:
return null;
}
@ -78,9 +77,7 @@ export const CustomDragLayer = ({ currentLayout }) => {
return (
<div style={layerStyles}>
<div style={getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)}>
{renderItem()}
</div>
<div style={getItemStyles(delta, item, initialOffset, currentOffset, currentLayout)}>{renderItem()}</div>
</div>
);
};

View file

@ -3,10 +3,9 @@ import { datasourceService, authenticationService } from '@/_services';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import { toast } from 'react-toastify';
import { dataBaseSources, apiSources, DataSourceTypes } from './DataSourceTypes';
import { defaultOptions } from './DefaultOptions';
import { TestConnection } from './TestConnection';
import { SourceComponents } from './SourceComponents';
import { DataBaseSources, ApiSources, DataSourceTypes, SourceComponents } from './SourceComponents';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import config from 'config';
@ -204,7 +203,7 @@ class DataSourceManager extends React.Component {
<div>
<div className="row row-deck">
<h4 className="text-muted mb-2">DATABASES</h4>
{dataBaseSources.map((dataSource) => (
{DataBaseSources.map((dataSource) => (
<div className="col-md-2" key={dataSource.name}>
<div className="card mb-3" role="button" onClick={() => this.selectDataSource(dataSource)}>
<div className="card-body">
@ -227,7 +226,7 @@ class DataSourceManager extends React.Component {
</div>
<div className="row row-deck mt-2">
<h4 className="text-muted mb-2">APIS</h4>
{apiSources.map((dataSource) => (
{ApiSources.map((dataSource) => (
<div className="col-md-2" key={dataSource.name}>
<div className="card" role="button" onClick={() => this.selectDataSource(dataSource)}>
<div className="card-body">
@ -292,7 +291,11 @@ class DataSourceManager extends React.Component {
<div className="col">
<small>
<a href={`https://docs.tooljet.io/docs/data-sources/${selectedDataSource.kind}`} target="_blank">
<a
href={`https://docs.tooljet.io/docs/data-sources/${selectedDataSource.kind}`}
target="_blank"
rel="noreferrer"
>
Read documentation
</a>
</small>

View file

@ -1,226 +0,0 @@
export const dataBaseSources = [
{
name: 'PostgreSQL',
kind: 'postgresql',
options: {
host: { type: 'string' },
port: { type: 'string' },
database: { type: 'string' },
username: { type: 'string' },
password: { type: 'string', encrypted: true }
},
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
}
},
{
name: 'MySQL',
kind: 'mysql',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
host: { type: 'string' },
port: { type: 'string' },
database: { type: 'string' },
username: { type: 'string' },
password: { type: 'string', encrypted: true }
}
},
{
name: 'SQL Server',
kind: 'mssql',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
host: { type: 'string' },
port: { type: 'string' },
database: { type: 'string' },
username: { type: 'string' },
password: { type: 'string', encrypted: true }
}
},
{
name: 'MongoDB',
kind: 'mongodb',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
host: { type: 'string' },
port: { type: 'string' },
username: { type: 'string' },
password: { type: 'string', encrypted: true },
connection_type: { type: 'options'},
connection_string: { type: 'string', encrypted: true }
}
},
{
name: 'Firestore',
kind: 'firestore',
exposedVariables: {
isLoading: {},
data: [],
rawData: []
},
options: {
gcp_key: { type: 'string', encrypted: true }
}
},
{
name: 'DynamoDB',
kind: 'dynamodb',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
region: { type: 'string' },
access_key: { type: 'string' },
secret_key: { type: 'string', encrypted: true }
}
},
{
name: 'Elasticsearch',
kind: 'elasticsearch',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
host: { type: 'string' },
port: { type: 'string' },
username: { type: 'string' },
password: { type: 'string', encrypted: true }
}
},
{
name: 'Redis',
kind: 'redis',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
host: { type: 'string' },
port: { type: 'string' },
username: { type: 'string' },
password: { type: 'string', encrypted: true }
}
}
];
export const apiSources = [
{
name: 'Rest API',
kind: 'restapi',
options: {
url: { type: 'string' },
auth_type: { type: 'string' },
grant_type: { type: 'string' },
add_token_to: { type: 'string' },
header_prefix: { type: 'string' },
access_token_url: { type: 'string' },
client_id: { type: 'string' },
client_secret: { type: 'string', encrypted: true },
scopes: { type: 'string' },
auth_url: { type: 'string' },
client_auth: { type: 'string' },
headers: { type: 'array' },
custom_auth_params: { type: 'array' }
},
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
customTesting: true
},
{
name: 'GraphQL',
kind: 'graphql',
options: {
url: { type: 'string' },
headers: { type: 'array' },
url_params: { type: 'array' },
body: { type: 'array' },
},
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
customTesting: true
},
{
name: 'Stripe',
kind: 'stripe',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
api_key: { type: 'string', encrypted: true }
},
customTesting: true
},
{
name: 'Airtable',
kind: 'airtable',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
api_key: { type: 'string', encrypted: true }
},
customTesting: true
},
{
name: 'Google Sheets',
kind: 'googlesheets',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
api_key: { type: 'string', encrypted: true }
},
customTesting: true,
hideSave: true
},
{
name: 'Slack',
kind: 'slack',
exposedVariables: {
isLoading: {},
data: {},
rawData: {}
},
options: {
api_key: { type: 'string', encrypted: true }
},
customTesting: true,
hideSave: true
}
];
export const DataSourceTypes = [
...dataBaseSources,
...apiSources
];

View file

@ -5,7 +5,7 @@ export const defaultOptions = {
database: { value: '' },
username: { value: '' },
password: { value: '' },
ssl_enabled: { value: true }
ssl_enabled: { value: true },
},
mysql: {
host: { value: 'localhost' },
@ -20,13 +20,13 @@ export const defaultOptions = {
port: { value: 1433 },
database: { value: '' },
username: { value: '' },
password: { value: '' }
password: { value: '' },
},
redis: {
host: { value: 'localhost' },
port: { value: 6379 },
username: { value: '' },
password: { value: '' }
password: { value: '' },
},
mongodb: {
database: { value: '' },
@ -35,7 +35,7 @@ export const defaultOptions = {
username: { value: '' },
password: { value: '' },
connection_type: { value: 'manual' },
connection_string: { value: ''}
connection_string: { value: '' },
},
elasticsearch: {
@ -43,16 +43,16 @@ export const defaultOptions = {
host: { value: 'localhost' },
port: { value: 9200 },
username: { value: '' },
password: { value: '' }
password: { value: '' },
},
stripe: {
api_key: { value: '' }
api_key: { value: '' },
},
airtable: {
api_key: { value: '' }
api_key: { value: '' },
},
firestore: {
gcp_key: { value: '' }
gcp_key: { value: '' },
},
restapi: {
url: { value: '' },
@ -67,22 +67,22 @@ export const defaultOptions = {
auth_url: { value: '' },
client_auth: { value: 'header' },
headers: { value: [['', '']] },
custom_auth_params: { value: [['', '']] }
custom_auth_params: { value: [['', '']] },
},
graphql: {
url: { value: '' },
headers: { value: [['', '']] },
url_params: { value: [['', '']] }
url_params: { value: [['', '']] },
},
googlesheets: {
access_type: { value: 'read' }
access_type: { value: 'read' },
},
slack: {
access_type: { value: 'read' }
access_type: { value: 'read' },
},
dynamodb: {
region: { value: ''},
access_key: { value: ''},
secret_key: { value: ''}
}
region: { value: '' },
access_key: { value: '' },
secret_key: { value: '' },
},
};

View file

@ -4,6 +4,19 @@
"title": "Airtable datasource",
"description": "A schema defining airtable datasource",
"type": "object",
"source": {
"name": "Airtable",
"kind": "airtable",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"api_key": { "type": "string", "encrypted": true }
},
"customTesting": true
},
"properties": {
"api_key": {
"$label": "API key",

View file

@ -4,6 +4,20 @@
"title": "Google Sheets datasource",
"description": "A schema defining google sheets datasource",
"type": "object",
"source": {
"name": "Google Sheets",
"kind": "googlesheets",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"api_key": { "type": "string", "encrypted": true }
},
"customTesting": true,
"hideSave": true
},
"properties": {
"sheets": {
"$label": "",

View file

@ -4,6 +4,22 @@
"title": "Graphql datasource",
"description": "A schema defining graphql datasource",
"type": "object",
"source": {
"name": "GraphQL",
"kind": "graphql",
"options": {
"url": { "type": "string" },
"headers": { "type": "array" },
"url_params": { "type": "array" },
"body": { "type": "array" }
},
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"customTesting": true
},
"properties": {
"url": {
"$label": "URL",

View file

@ -4,6 +4,31 @@
"title": "Restapi datasource",
"description": "A schema defining restapi datasource",
"type": "object",
"source": {
"name": "Rest API",
"kind": "restapi",
"options": {
"url": { "type": "string" },
"auth_type": { "type": "string" },
"grant_type": { "type": "string" },
"add_token_to": { "type": "string" },
"header_prefix": { "type": "string" },
"access_token_url": { "type": "string" },
"client_id": { "type": "string" },
"client_secret": { "type": "string", "encrypted": true },
"scopes": { "type": "string" },
"auth_url": { "type": "string" },
"client_auth": { "type": "string" },
"headers": { "type": "array" },
"custom_auth_params": { "type": "array" }
},
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"customTesting": true
},
"properties": {
"url": {
"$label": "URL",

View file

@ -4,6 +4,20 @@
"title": "Slack datasource",
"description": "A schema defining slack datasource",
"type": "object",
"source": {
"name": "Slack",
"kind": "slack",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"api_key": { "type": "string", "encrypted": true }
},
"customTesting": true,
"hideSave": true
},
"properties": {
"sheets": {
"$label": "",

View file

@ -4,6 +4,19 @@
"title": "Stripe datasource",
"description": "A schema defining stripe datasource",
"type": "object",
"source": {
"name": "Stripe",
"kind": "stripe",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"api_key": { "type": "string", "encrypted": true }
},
"customTesting": true
},
"properties": {
"api_key": {
"$label": "API key",

View file

@ -4,6 +4,20 @@
"title": "Dynamodb datasource",
"description": "A schema defining dynamodb datasource",
"type": "object",
"source": {
"name": "DynamoDB",
"kind": "dynamodb",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"region": { "type": "string" },
"access_key": { "type": "string" },
"secret_key": { "type": "string", "encrypted": true }
}
},
"properties": {
"region": {
"$label": "Region",

View file

@ -4,6 +4,21 @@
"title": "Elastic search datasource",
"description": "A schema defining elastic search datasource",
"type": "object",
"source": {
"name": "Elasticsearch",
"kind": "elasticsearch",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"host": { "type": "string" },
"port": { "type": "string" },
"username": { "type": "string" },
"password": { "type": "string", "encrypted": true }
}
},
"properties": {
"host": {
"$label": "Host",
@ -21,13 +36,13 @@
"$label": "Username",
"$key": "username",
"type": "text",
"description": "Enter username field"
"description": "Enter username"
},
"password": {
"$label": "Password",
"$key": "password",
"type": "password",
"description": "Enter password field"
"description": "Enter password"
}
},
"required": ["scheme", "host", "port", "password"]

View file

@ -4,6 +4,18 @@
"title": "Firestore datasource",
"description": "A schema defining firestore datasource",
"type": "object",
"source": {
"name": "Firestore",
"kind": "firestore",
"exposedVariables": {
"isLoading": {},
"data": [],
"rawData": []
},
"options": {
"gcp_key": { "type": "string", "encrypted": true }
}
},
"properties": {
"gcp_key": {
"$label": "Private key",

View file

@ -4,6 +4,23 @@
"title": "Mongodb datasource",
"description": "A schema defining mongodb datasource",
"type": "object",
"source": {
"name": "MongoDB",
"kind": "mongodb",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"host": { "type": "string" },
"port": { "type": "string" },
"username": { "type": "string" },
"password": { "type": "string", "encrypted": true },
"connection_type": { "type": "options" },
"connection_string": { "type": "string", "encrypted": true }
}
},
"properties": {
"connection_type": {
"$label": "",
@ -38,13 +55,13 @@
"$label": "Username",
"$key": "username",
"type": "text",
"description": "Enter username field"
"description": "Enter username"
},
"password": {
"$label": "Password",
"$key": "password",
"type": "password",
"description": "Enter password field"
"description": "Enter password"
}
},
"string": {

View file

@ -4,6 +4,22 @@
"title": "Mssql datasource",
"description": "A schema defining mssql datasource",
"type": "object",
"source": {
"name": "SQL Server",
"kind": "mssql",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"host": { "type": "string" },
"port": { "type": "string" },
"database": { "type": "string" },
"username": { "type": "string" },
"password": { "type": "string", "encrypted": true }
}
},
"properties": {
"host": {
"$label": "Host",
@ -27,19 +43,19 @@
"$label": "Username",
"$key": "username",
"type": "text",
"description": "Enter username field"
"description": "Enter username"
},
"password": {
"$label": "Password",
"$key": "password",
"type": "password",
"description": "Enter password field"
"description": "Enter password"
},
"azure": {
"$label": "Azure",
"$key": "azure",
"type": "toggle",
"description": "Toggle for azure"
"description": "Toggle for azure"
}
},
"required": ["host", "port", "username", "password"]

View file

@ -4,6 +4,22 @@
"title": "Mysql datasource",
"description": "A schema defining mysql datasource",
"type": "object",
"source": {
"name": "MySQL",
"kind": "mysql",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"host": { "type": "string" },
"port": { "type": "string" },
"database": { "type": "string" },
"username": { "type": "string" },
"password": { "type": "string", "encrypted": true }
}
},
"properties": {
"host": {
"$label": "Host",
@ -33,13 +49,13 @@
"$label": "Username",
"$key": "username",
"type": "text",
"description": "Enter username field"
"description": "Enter username"
},
"password": {
"$label": "Password",
"$key": "password",
"type": "password",
"description": "Enter password field"
"description": "Enter password"
}
},
"required": ["host", "port", "username", "password"]

View file

@ -4,6 +4,22 @@
"title": "Postgresql datasource",
"description": "A schema defining postgresql datasource",
"type": "object",
"source": {
"name": "PostgreSQL",
"kind": "postgresql",
"options": {
"host": { "type": "string" },
"port": { "type": "string" },
"database": { "type": "string" },
"username": { "type": "string" },
"password": { "type": "string", "encrypted": true }
},
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
}
},
"properties": {
"host": {
"$label": "Host",
@ -33,13 +49,13 @@
"$label": "Username",
"$key": "username",
"type": "text",
"description": "Enter username field"
"description": "Enter username"
},
"password": {
"$label": "Password",
"$key": "password",
"type": "password",
"description": "Enter password field"
"description": "Enter password"
}
},
"required": ["host", "port", "username", "database", "password"]

View file

@ -4,6 +4,21 @@
"title": "Redis datasource",
"description": "A schema defining redis datasource",
"type": "object",
"source": {
"name": "Redis",
"kind": "redis",
"exposedVariables": {
"isLoading": {},
"data": {},
"rawData": {}
},
"options": {
"host": { "type": "string" },
"port": { "type": "string" },
"username": { "type": "string" },
"password": { "type": "string", "encrypted": true }
}
},
"properties": {
"host": {
"$label": "Host",
@ -21,13 +36,13 @@
"$label": "Username",
"$key": "username",
"type": "text",
"description": "Enter username field"
"description": "Enter username"
},
"password": {
"$label": "Password",
"$key": "password",
"type": "password",
"description": "Enter password field"
"description": "Enter password"
}
},
"required": ["scheme", "host", "port", "password"]

View file

@ -1,8 +1,6 @@
import React from 'react';
export const Mysql = ({
optionchanged, options
}) => {
export const Mysql = ({ optionchanged, options }) => {
return (
<div>
<div className="row">
@ -25,9 +23,7 @@ export const Mysql = ({
/>
</div>
<div className="col-md-2">
<label className="form-label">
SSL
</label>
<label className="form-label">SSL</label>
<label className="form-check form-switch mt-3">
<input
className="form-check-input"
@ -61,8 +57,13 @@ export const Mysql = ({
<label className="form-label">
Password
<small className="text-green mx-2">
<img className="mx-2 encrypted-icon encrypted-icon" src="/assets/images/icons/padlock.svg" width="12" height="12" />
<span className="pt-2">Encrypted</span>
<img
className="mx-2 encrypted-icon encrypted-icon"
src="/assets/images/icons/padlock.svg"
width="12"
height="12"
/>
<span className="pt-2">Encrypted</span>
</small>
</label>
<input

View file

@ -1,38 +1,76 @@
import React from 'react';
import React from "react";
import DynamicForm from '@/_components/DynamicForm';
import DynamicForm from "@/_components/DynamicForm";
import AirtableSchema from './Api/Airtable.schema.json';
import RestapiSchema from './Api/Restapi.schema.json';
import GraphqlSchema from './Api/Graphql.schema.json';
import StripeSchema from './Api/Stripe.schema.json';
import GooglesheetSchema from './Api/Googlesheets.schema.json';
import SlackSchema from './Api/Slack.schema.json';
import AirtableSchema from "./Api/Airtable.schema.json";
import RestapiSchema from "./Api/Restapi.schema.json";
import GraphqlSchema from "./Api/Graphql.schema.json";
import StripeSchema from "./Api/Stripe.schema.json";
import GooglesheetSchema from "./Api/Googlesheets.schema.json";
import SlackSchema from "./Api/Slack.schema.json";
import DynamodbSchema from './Database/Dynamodb.schema.json';
import ElasticsearchSchema from './Database/Elasticsearch.schema.json';
import RedisSchema from './Database/Redis.schema.json';
import FirestoreSchema from './Database/Firestore.schema.json';
import MongodbSchema from './Database/Mongodb.schema.json';
import PostgresqlSchema from './Database/Postgresql.schema.json';
import MysqlSchema from './Database/Mysql.schema.json';
import MssqlSchema from './Database/Mssql.schema.json';
import DynamodbSchema from "./Database/Dynamodb.schema.json";
import ElasticsearchSchema from "./Database/Elasticsearch.schema.json";
import RedisSchema from "./Database/Redis.schema.json";
import FirestoreSchema from "./Database/Firestore.schema.json";
import MongodbSchema from "./Database/Mongodb.schema.json";
import PostgresqlSchema from "./Database/Postgresql.schema.json";
import MysqlSchema from "./Database/Mysql.schema.json";
import MssqlSchema from "./Database/Mssql.schema.json";
const Airtable = ({ ...rest }) => <DynamicForm schema={AirtableSchema} {...rest} />;
const Restapi = ({ ...rest }) => <DynamicForm schema={RestapiSchema} {...rest} />;
const Graphql = ({ ...rest }) => <DynamicForm schema={GraphqlSchema} {...rest} />;
const Airtable = ({ ...rest }) => (
<DynamicForm schema={AirtableSchema} {...rest} />
);
const Restapi = ({ ...rest }) => (
<DynamicForm schema={RestapiSchema} {...rest} />
);
const Graphql = ({ ...rest }) => (
<DynamicForm schema={GraphqlSchema} {...rest} />
);
const Stripe = ({ ...rest }) => <DynamicForm schema={StripeSchema} {...rest} />;
const Googlesheets = ({ ...rest }) => <DynamicForm schema={GooglesheetSchema} {...rest} />;
const Googlesheets = ({ ...rest }) => (
<DynamicForm schema={GooglesheetSchema} {...rest} />
);
const Slack = ({ ...rest }) => <DynamicForm schema={SlackSchema} {...rest} />;
const Dynamodb = ({ ...rest }) => <DynamicForm schema={DynamodbSchema} {...rest} />;
const Elasticsearch = ({ ...rest }) => <DynamicForm schema={ElasticsearchSchema} {...rest} />;
const Dynamodb = ({ ...rest }) => (
<DynamicForm schema={DynamodbSchema} {...rest} />
);
const Elasticsearch = ({ ...rest }) => (
<DynamicForm schema={ElasticsearchSchema} {...rest} />
);
const Redis = ({ ...rest }) => <DynamicForm schema={RedisSchema} {...rest} />;
const Firestore = ({ ...rest }) => <DynamicForm schema={FirestoreSchema} {...rest} />;
const Mongodb = ({ ...rest }) => <DynamicForm schema={MongodbSchema} {...rest} />;
const Postgresql = ({ ...rest }) => <DynamicForm schema={PostgresqlSchema} {...rest} />;
const Firestore = ({ ...rest }) => (
<DynamicForm schema={FirestoreSchema} {...rest} />
);
const Mongodb = ({ ...rest }) => (
<DynamicForm schema={MongodbSchema} {...rest} />
);
const Postgresql = ({ ...rest }) => (
<DynamicForm schema={PostgresqlSchema} {...rest} />
);
const Mysql = ({ ...rest }) => <DynamicForm schema={MysqlSchema} {...rest} />;
const Mssql = ({ ...rest }) => <DynamicForm schema={MssqlSchema} {...rest} />;
export const DataBaseSources = [
DynamodbSchema.source,
ElasticsearchSchema.source,
RedisSchema.source,
FirestoreSchema.source,
MongodbSchema.source,
PostgresqlSchema.source,
MysqlSchema.source,
MssqlSchema.source,
];
export const ApiSources = [
AirtableSchema.source,
RestapiSchema.source,
GraphqlSchema.source,
StripeSchema.source,
GooglesheetSchema.source,
SlackSchema.source,
];
export const DataSourceTypes = [...DataBaseSources, ...ApiSources];
export const SourceComponents = {
Elasticsearch,
Redis,
@ -47,5 +85,5 @@ export const SourceComponents = {
Airtable,
Graphql,
Mysql,
Mssql
Mssql,
};

View file

@ -1,7 +1,6 @@
import React, { useEffect, useState } from 'react';
import Button from 'react-bootstrap/Button';
import { toast } from 'react-toastify';
import { ToastContainer } from 'react-toastify';
import { toast, ToastContainer } from 'react-toastify';
import { datasourceService } from '@/_services';
export const TestConnection = ({ kind, options, onConnectionTestFailed }) => {
@ -29,7 +28,7 @@ export const TestConnection = ({ kind, options, onConnectionTestFailed }) => {
datasourceService.test(kind, options).then(
(data) => {
setTestingStatus(false);
if(data.status === 'ok') {
if (data.status === 'ok') {
setConnectionStatus('success');
} else {
setConnectionStatus('failed');

View file

@ -1,9 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from 'react';
import { useDrag } from 'react-dnd';
import { ItemTypes } from './ItemTypes';
import { getEmptyImage } from 'react-dnd-html5-backend';
import { Box } from './Box';
import { Resizable } from 're-resizable';
import { ConfigHandle } from './ConfigHandle';
import { Rnd } from 'react-rnd';
@ -45,14 +45,13 @@ function getStyles(left, top, isDragging, component, isSelectedComponent) {
// const transform = `translate3d(${left}px, ${top}px, 0)`;
return {
position: 'absolute',
height: '100%',
// transform,
// WebkitTransform: transform,
zIndex: isSelectedComponent ? 2 : 1,
// IE fallback: hide the real node using CSS when dragging
// because IE will ignore our custom "empty image" drag preview.
opacity: isDragging ? 0 : 1,
height: isDragging ? 0 : '',
height: isDragging ? 0 : '100%',
};
}
@ -62,8 +61,6 @@ export const DraggableBox = function DraggableBox({
title,
left,
top,
width,
height,
parent,
component,
index,

View file

@ -7,7 +7,7 @@ import { CustomDragLayer } from './CustomDragLayer';
import { LeftSidebar } from './LeftSidebar';
import { componentTypes } from './Components/components';
import { Inspector } from './Inspector/Inspector';
import { DataSourceTypes } from './DataSourceManager/DataSourceTypes';
import { DataSourceTypes } from './DataSourceManager/SourceComponents';
import { QueryManager } from './QueryManager';
import { toast } from 'react-toastify';
import { Link } from 'react-router-dom';
@ -22,7 +22,7 @@ import {
onQueryCancel,
runQuery,
setStateAsync,
computeComponentState
computeComponentState,
} from '@/_helpers/appUtils';
import { Confirm } from './Viewer/Confirm';
import ReactTooltip from 'react-tooltip';
@ -74,7 +74,7 @@ class Editor extends React.Component {
currentUser: userVars,
urlparams: JSON.parse(JSON.stringify(queryString.parse(props.location.search))),
},
errors: {}
errors: {},
},
apps: [],
dataQueriesDefaultText: "You haven't created queries yet.",
@ -89,7 +89,7 @@ class Editor extends React.Component {
this.fetchApps(0);
appService.getApp(appId).then((data) => {
const dataDefinition = data.definition || {components: {}}
const dataDefinition = data.definition || { components: {} };
this.setState(
{
app: data,
@ -107,7 +107,7 @@ class Editor extends React.Component {
computeComponentState(this, this.state.appDefinition.components);
}
)
);
});
this.fetchDataSources();
@ -192,11 +192,11 @@ class Editor extends React.Component {
};
setAppDefinitionFromVersion = (version) => {
this.appDefinitionChanged(version.definition || {components: {}})
this.appDefinitionChanged(version.definition || { components: {} });
this.setState({
editingVersion: version
})
}
editingVersion: version,
});
};
dataSourcesChanged = () => {
this.fetchDataSources();
@ -274,7 +274,7 @@ class Editor extends React.Component {
...this.state.appDefinition.components,
[newDefinition.id]: {
...this.state.appDefinition.components[newDefinition.id],
component: newDefinition.component
component: newDefinition.component,
},
},
},
@ -328,11 +328,11 @@ class Editor extends React.Component {
deleteDataQuery = () => {
this.setState({ showDataQueryDeletionConfirmation: true });
}
};
cancelDeleteDataQuery = () => {
this.setState({ showDataQueryDeletionConfirmation: false});
}
this.setState({ showDataQueryDeletionConfirmation: false });
};
executeDataQueryDeletion = () => {
this.setState({ showDataQueryDeletionConfirmation: false, isDeletingDataQuery: true });
@ -351,7 +351,7 @@ class Editor extends React.Component {
setShowHiddenOptionsForDataQuery = (dataQueryId) => {
this.setState({ showHiddenOptionsForDataQueryId: dataQueryId });
}
};
renderDataQuery = (dataQuery) => {
const sourceMeta = DataSourceTypes.find((source) => source.kind === dataQuery.kind);
@ -360,7 +360,7 @@ class Editor extends React.Component {
if (this.state.selectedQuery) {
isSeletedQuery = dataQuery.id === this.state.selectedQuery.id;
}
const isQueryBeingDeleted = this.state.isDeletingDataQuery && isSeletedQuery
const isQueryBeingDeleted = this.state.isDeletingDataQuery && isSeletedQuery;
const { currentState } = this.state;
const isLoading = currentState.queries[dataQuery.name] ? currentState.queries[dataQuery.name].isLoading : false;
@ -376,6 +376,7 @@ class Editor extends React.Component {
>
<div className="col">
<img
className="svg-icon"
src={`/assets/images/icons/editor/datasources/${sourceMeta.kind.toLowerCase()}.svg`}
width="20"
height="20"
@ -383,7 +384,7 @@ class Editor extends React.Component {
<span className="p-3">{dataQuery.name}</span>
</div>
<div className="col-auto mx-1">
{ isQueryBeingDeleted ? (
{isQueryBeingDeleted ? (
<div className="px-2">
<div className="text-center spinner-border spinner-border-sm" role="status"></div>
</div>
@ -518,7 +519,7 @@ class Editor extends React.Component {
showDataQueryDeletionConfirmation,
isDeletingDataQuery,
apps,
defaultComponentStateComputed
defaultComponentStateComputed,
} = this.state;
const appLink = slug ? `/applications/${slug}` : '';
@ -651,7 +652,7 @@ class Editor extends React.Component {
app={app}
darkMode={this.props.darkMode}
onVersionDeploy={this.onVersionDeploy}
editingVersionId={this.state.editingVersion ? this.state.editingVersion.id : null }
editingVersionId={this.state.editingVersion ? this.state.editingVersion.id : null}
setAppDefinitionFromVersion={this.setAppDefinitionFromVersion}
/>
)}
@ -679,7 +680,7 @@ class Editor extends React.Component {
style={{ transform: `scale(${zoomLevel})` }}
>
<div className="canvas-area" style={{ width: currentLayout === 'desktop' ? '1292px' : '450px' }}>
{defaultComponentStateComputed &&
{defaultComponentStateComputed && (
<Container
appDefinition={appDefinition}
appDefinitionChanged={this.appDefinitionChanged}
@ -708,7 +709,7 @@ class Editor extends React.Component {
onComponentClick(this, id, component);
}}
/>
}
)}
<CustomDragLayer snapToGrid={true} currentLayout={currentLayout} />
</div>
</div>
@ -728,19 +729,25 @@ class Editor extends React.Component {
<h5 className="py-1 px-3 text-muted">QUERIES</h5>
</div>
<div className="col-auto px-3">
<button className="btn btn-sm btn-light mx-2" onClick={this.toggleQuerySearch}>
<button
className="btn btn-sm btn-light mx-2"
data-class="py-1 px-2"
data-tip="Search query"
onClick={this.toggleQuerySearch}>
<img className="py-1" src="/assets/images/icons/lens.svg" width="17" height="17" />
</button>
<span
data-tip="Add new query"
data-class="py-1 px-2"
className="btn btn-sm btn-light text-muted"
onClick={() => this.setState({
selectedQuery: {},
editingQuery: false,
addingQuery: true
})}
className="btn btn-sm btn-light btn-px-1 text-muted"
onClick={() =>
this.setState({
selectedQuery: {},
editingQuery: false,
addingQuery: true,
})
}
>
+
</span>

View file

@ -2,44 +2,39 @@ import React, { useState, useEffect } from 'react';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
export function GotoApp({
getAllApps,
currentState,
event,
handlerChanged,
eventIndex
}) {
export function GotoApp({ getAllApps, currentState, event, handlerChanged, eventIndex }) {
const queryParamChangeHandler = (index, key, value) => {
event.queryParams[index][key] = value
handlerChanged(eventIndex, 'queryParams', event.queryParams)
}
event.queryParams[index][key] = value;
handlerChanged(eventIndex, 'queryParams', event.queryParams);
};
const addQueryParam = () => {
if (!event.queryParams) {
event.queryParams = []
handlerChanged(eventIndex, 'queryParams', event.queryParams)
event.queryParams = [];
handlerChanged(eventIndex, 'queryParams', event.queryParams);
}
event.queryParams.push(['', ''])
handlerChanged(eventIndex, 'queryParams', event.queryParams)
setNumberOfQueryparams(numberOfQueryParams + 1)
}
event.queryParams.push(['', '']);
handlerChanged(eventIndex, 'queryParams', event.queryParams);
setNumberOfQueryparams(numberOfQueryParams + 1);
};
const deleteQueryParam = index => {
event.queryParams.splice(index, 1)
handlerChanged(eventIndex, 'queryParams', event.queryParams)
setNumberOfQueryparams(numberOfQueryParams - 1)
}
const deleteQueryParam = (index) => {
event.queryParams.splice(index, 1);
handlerChanged(eventIndex, 'queryParams', event.queryParams);
setNumberOfQueryparams(numberOfQueryParams - 1);
};
const [numberOfQueryParams, setNumberOfQueryparams] = useState(0)
const [numberOfQueryParams, setNumberOfQueryparams] = useState(0);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (event.queryParams) {
setNumberOfQueryparams(event.queryParams.length)
setNumberOfQueryparams(event.queryParams.length);
}
})
});
return(
return (
<div className="p-1">
<label className="form-label mt-1">App</label>
<SelectSearch
@ -54,38 +49,37 @@ export function GotoApp({
/>
<label className="form-label mt-2">Query params</label>
{Array(numberOfQueryParams).fill(0).map((_, index) =>
<div key={index}>
<div className="input-group mt-1">
<CodeHinter
currentState={currentState}
initialValue={event.queryParams[index][0]}
onChange={(value) => queryParamChangeHandler(index, 0, value)}
mode='javascript'
className="form-control codehinter-query-editor-input"
height={30}
/>
<CodeHinter
currentState={currentState}
initialValue={event.queryParams[index][1]}
onChange={(value) => queryParamChangeHandler(index, 1, value)}
mode='javascript'
className="form-control codehinter-query-editor-input"
height={30}
/>
<span
className="input-group-text btn-sm"
role="button"
onClick={() => deleteQueryParam(index)}
>x</span>
{Array(numberOfQueryParams)
.fill(0)
.map((_, index) => (
<div key={index}>
<div className="input-group mt-1">
<CodeHinter
currentState={currentState}
initialValue={event.queryParams[index][0]}
onChange={(value) => queryParamChangeHandler(index, 0, value)}
mode="javascript"
className="form-control codehinter-query-editor-input"
height={30}
/>
<CodeHinter
currentState={currentState}
initialValue={event.queryParams[index][1]}
onChange={(value) => queryParamChangeHandler(index, 1, value)}
mode="javascript"
className="form-control codehinter-query-editor-input"
height={30}
/>
<span className="input-group-text btn-sm" role="button" onClick={() => deleteQueryParam(index)}>
x
</span>
</div>
</div>
</div>
)}
))}
<button
className="btn btn-sm btn-outline-azure mt-2 mx-0 mb-0"
onClick={addQueryParam}
>+</button>
<button className="btn btn-sm btn-outline-azure mt-2 mx-0 mb-0" onClick={addQueryParam}>
+
</button>
</div>
)
);
}

View file

@ -1,6 +1,5 @@
import React from 'react';
import { renderElement } from '../Utils';
import { Color } from '../Elements/Color';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
class Chart extends React.Component {
@ -8,7 +7,14 @@ class Chart extends React.Component {
super(props);
const {
dataQueries, component, paramUpdated, componentMeta, eventUpdated, eventOptionUpdated, components, currentState
dataQueries,
component,
paramUpdated,
componentMeta,
eventUpdated,
eventOptionUpdated,
components,
currentState,
} = props;
this.state = {
@ -19,7 +25,7 @@ class Chart extends React.Component {
eventUpdated,
eventOptionUpdated,
components,
currentState
currentState,
};
}
@ -32,7 +38,7 @@ class Chart extends React.Component {
eventUpdated,
eventOptionUpdated,
components,
currentState
currentState,
} = this.props;
this.setState({
@ -43,48 +49,67 @@ class Chart extends React.Component {
eventUpdated,
eventOptionUpdated,
components,
currentState
currentState,
});
}
render() {
const {
dataQueries,
component,
paramUpdated,
componentMeta,
eventUpdated,
eventOptionUpdated,
components,
currentState
} = this.state;
const { dataQueries, component, paramUpdated, componentMeta, components, currentState } = this.state;
const data = this.state.component.component.definition.properties.data;
return (
<div className="properties-container p-2">
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'title', 'properties', currentState, components)}
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'type', 'properties', currentState, components)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'title',
'properties',
currentState,
components
)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'type',
'properties',
currentState,
components
)}
<div className="field mb-3 chart-data-input">
<label className="form-label">Chart data</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={data.value}
theme={this.props.darkMode ? 'monokai' : 'duotone-light'}
mode= "javascript"
lineNumbers={false}
className="chart-input pr-2"
onChange={(value) => this.props.paramUpdated({ name: 'data' }, 'value', value, 'properties')}
/>
currentState={this.props.currentState}
initialValue={data.value}
theme={this.props.darkMode ? 'monokai' : 'duotone-light'}
mode="javascript"
lineNumbers={false}
className="chart-input pr-2"
onChange={(value) => this.props.paramUpdated({ name: 'data' }, 'value', value, 'properties')}
/>
</div>
{Object.keys(componentMeta.styles).map((style) => renderElement(component, componentMeta, paramUpdated, dataQueries, style, 'styles', currentState, components))}
{Object.keys(componentMeta.styles).map((style) =>
renderElement(component, componentMeta, paramUpdated, dataQueries, style, 'styles', currentState, components)
)}
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'loadingState', 'properties', currentState)}
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'markerColor', 'properties', currentState)}
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'showGridLines', 'properties', currentState)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'showGridLines',
'properties',
currentState
)}
</div>
);
}

View file

@ -7,7 +7,7 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import { Color } from '../Elements/Color';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import { v4 as uuidv4 } from 'uuid';
import { v4 as uuidv4 } from 'uuid';
import { EventManager } from '../EventManager';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
@ -16,7 +16,14 @@ class Table extends React.Component {
super(props);
const {
dataQueries, component, paramUpdated, componentMeta, eventUpdated, eventOptionUpdated, components, currentState
dataQueries,
component,
paramUpdated,
componentMeta,
eventUpdated,
eventOptionUpdated,
components,
currentState,
} = props;
this.state = {
@ -29,7 +36,7 @@ class Table extends React.Component {
components,
currentState,
actionPopOverRootClose: true,
showPopOver: false
showPopOver: false,
};
}
@ -42,7 +49,7 @@ class Table extends React.Component {
eventUpdated,
eventOptionUpdated,
components,
currentState
currentState,
} = this.props;
this.setState({
@ -53,7 +60,7 @@ class Table extends React.Component {
eventUpdated,
eventOptionUpdated,
components,
currentState
currentState,
});
}
@ -65,9 +72,9 @@ class Table extends React.Component {
actionButtonEventsChanged = (events, index) => {
let actions = this.props.component.component.definition.properties.actions.value;
actions[index]['events'] = events
actions[index]['events'] = events;
this.props.paramUpdated({ name: 'actions' }, 'value', actions, 'properties');
}
};
actionButtonEventUpdated = (event, value, extraData) => {
const actions = this.props.component.component.definition.properties.actions;
@ -75,7 +82,7 @@ class Table extends React.Component {
let newValues = actions.value;
newValues[index][event.name] = {
actionId: value
actionId: value,
};
this.props.paramUpdated({ name: 'actions' }, 'value', newValues, 'properties');
@ -90,7 +97,7 @@ class Table extends React.Component {
newValues[index][event.name].options = {
...options,
[option]: value
[option]: value,
};
this.props.paramUpdated({ name: 'actions' }, 'value', newValues, 'properties');
@ -114,7 +121,7 @@ class Table extends React.Component {
{ name: 'Radio', value: 'radio' },
{ name: 'Multiselect', value: 'multiselect' },
{ name: 'Toggle switch', value: 'toggle' },
{ name: 'Date Picker', value: 'datepicker' }
{ name: 'Date Picker', value: 'datepicker' },
]}
value={column.columnType}
search={true}
@ -152,93 +159,95 @@ class Table extends React.Component {
/>
</div>
{(column.columnType === 'string' || column.columnType === undefined || column.columnType === 'default') &&
<div>
<div className="field mb-2">
<label className="form-label">Text color</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.textColor}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode= "javascript"
lineNumbers={false}
placeholder={'Text color of the cell'}
onChange={(value) => this.onColumnItemChange(index, 'textColor', value)}
/>
</div>
{column.isEditable &&
<div>
<div className="hr-text">Validation</div>
<div className="field mb-2">
<label className="form-label">Regex</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.regex}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode= "javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'regex', value)}
/>
</div>
<div className="field mb-2">
<label className="form-label">Min length</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.minLength}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode= "javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'minLength', value)}
/>
</div>
<div className="field mb-2">
<label className="form-label">Max length</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.maxLength}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode= "javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'maxLength', value)}
/>
</div>
<div className="field mb-2">
<label className="form-label">Custom rule</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.customRule}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode= "javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'customRule', value)}
/>
</div>
{(column.columnType === 'string' || column.columnType === undefined || column.columnType === 'default') && (
<div>
<div className="field mb-2">
<label className="form-label">Text color</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.textColor}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={'Text color of the cell'}
onChange={(value) => this.onColumnItemChange(index, 'textColor', value)}
/>
</div>
}
</div>
}
{column.isEditable && (
<div>
<div className="hr-text">Validation</div>
<div className="field mb-2">
<label className="form-label">Regex</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.regex}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'regex', value)}
/>
</div>
<div className="field mb-2">
<label className="form-label">Min length</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.minLength}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'minLength', value)}
/>
</div>
<div className="field mb-2">
<label className="form-label">Max length</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.maxLength}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'maxLength', value)}
/>
</div>
<div className="field mb-2">
<label className="form-label">Custom rule</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.customRule}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'customRule', value)}
/>
</div>
</div>
)}
</div>
)}
{column.columnType === 'toggle' &&
{column.columnType === 'toggle' && (
<div>
<div className="field mb-2">
<Color
param={{ name: 'Active color' }}
paramType="properties"
componentMeta={{ properties: { color: { displayName: 'Active color'} } }}
componentMeta={{ properties: { color: { displayName: 'Active color' } } }}
definition={{ value: column.activeColor || '#3c92dc' }}
onChange={(name, value, color) => this.onColumnItemChange(index, 'activeColor', color)}
/>
</div>
</div>
}
)}
{(column.columnType === 'dropdown' || column.columnType === 'multiselect' || column.columnType === 'badge' || column.columnType === 'badges' || column.columnType === 'radio') && (
{(column.columnType === 'dropdown' ||
column.columnType === 'multiselect' ||
column.columnType === 'badge' ||
column.columnType === 'badges' ||
column.columnType === 'radio') && (
<div>
<div className="field mb-2">
<label className="form-label">Values</label>
@ -246,7 +255,7 @@ class Table extends React.Component {
currentState={this.props.currentState}
initialValue={column.values}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode= "javascript"
mode="javascript"
lineNumbers={false}
placeholder={'{{[1, 2, 3]}}'}
onChange={(value) => this.onColumnItemChange(index, 'values', value)}
@ -258,7 +267,7 @@ class Table extends React.Component {
currentState={this.props.currentState}
initialValue={column.labels}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode= "javascript"
mode="javascript"
lineNumbers={false}
placeholder={'{{["one", "two", "three"]}}'}
onChange={(value) => this.onColumnItemChange(index, 'labels', value)}
@ -267,27 +276,27 @@ class Table extends React.Component {
</div>
)}
{column.columnType === 'dropdown' &&
{column.columnType === 'dropdown' && (
<>
{column.isEditable &&
<div>
<div className="hr-text">Validation</div>
<div className="field mb-2">
<label className="form-label">Custom rule</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.customRule}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode= "javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'customRule', value)}
/>
{column.isEditable && (
<div>
<div className="hr-text">Validation</div>
<div className="field mb-2">
<label className="form-label">Custom rule</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.customRule}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={''}
onChange={(value) => this.onColumnItemChange(index, 'customRule', value)}
/>
</div>
</div>
</div>
}
)}
</>
}
)}
{column.columnType === 'datepicker' && (
<div>
@ -304,22 +313,20 @@ class Table extends React.Component {
placeholder={'DD-MM-YYYY'}
/>
</div>
<div className="field mb-2">
<div className="field mb-2">
<label className="form-check form-switch my-2">
<input
className="form-check-input"
type="checkbox"
onClick={() => {
this.onColumnItemChange(index, 'isTimeChecked', !column.isTimeChecked)
this.onColumnItemChange(index, 'isTimeChecked', !column.isTimeChecked);
}}
checked={column.isTimeChecked}
/>
<span className="form-check-label">show time</span>
</label>
</div>
</div>
)}
<label className="form-check form-switch my-4">
@ -340,10 +347,10 @@ class Table extends React.Component {
const dummyComponentForActionButton = {
component: {
definition: {
events: this.props.component.component.definition.properties.actions.value[index].events || []
}
}
}
events: this.props.component.component.definition.properties.actions.value[index].events || [],
},
},
};
return (
<Popover id="popover-basic">
@ -370,7 +377,7 @@ class Table extends React.Component {
value={action.position ?? 'right'}
search={false}
closeOnSelect={true}
onChange={value => {
onChange={(value) => {
this.onActionButtonPropertyChanged(index, 'position', value);
}}
filterOptions={fuzzySearch}
@ -394,15 +401,15 @@ class Table extends React.Component {
/>
<EventManager
component={dummyComponentForActionButton}
componentMeta={{events: { onClick: {displayName: 'On click' }}}}
componentMeta={{ events: { onClick: { displayName: 'On click' } } }}
currentState={this.state.currentState}
dataQueries={this.props.dataQueries}
components={this.props.components}
eventsChanged={events => this.actionButtonEventsChanged(events, index)}
eventsChanged={(events) => this.actionButtonEventsChanged(events, index)}
apps={this.props.apps}
popOverCallback={(showing) => {
this.setState({actionPopOverRootClose: !showing})
this.setState({showPopOver: showing})
this.setState({ actionPopOverRootClose: !showing });
this.setState({ showPopOver: showing });
}}
/>
<button className="btn btn-sm btn-outline-danger mt-2 col" onClick={() => this.removeAction(index)}>
@ -420,7 +427,7 @@ class Table extends React.Component {
placement="left"
rootClose={this.state.actionPopOverRootClose}
overlay={this.actionPopOver(action, index)}
onToggle={showing => this.setState({showPopOver: showing})}
onToggle={(showing) => this.setState({ showPopOver: showing })}
>
<div className={`card p-2 ${this.props.darkMode ? 'bg-secondary' : 'bg-light'}`} role="button">
<div className={`row ${this.props.darkMode ? '' : 'bg-light'}`}>
@ -446,14 +453,14 @@ class Table extends React.Component {
while (!found) {
columnName = `new_column${currentNumber}`;
if (columns.find(column => column.name === columnName) === undefined) {
if (columns.find((column) => column.name === columnName) === undefined) {
found = true;
}
currentNumber += 1;
}
return columnName;
}
};
addNewColumn = () => {
const columns = this.props.component.component.definition.properties.columns;
@ -506,16 +513,7 @@ class Table extends React.Component {
};
render() {
const {
dataQueries,
component,
paramUpdated,
componentMeta,
eventUpdated,
eventOptionUpdated,
components,
currentState
} = this.props;
const { dataQueries, component, paramUpdated, componentMeta, components, currentState } = this.props;
const columns = component.component.definition.properties.columns;
const actions = component.component.definition.properties.actions || { value: [] };
@ -527,7 +525,16 @@ class Table extends React.Component {
return (
<div className="properties-container p-2 " key={this.props.component.id}>
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'data', 'properties', currentState, components)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'data',
'properties',
currentState,
components
)}
<div className="field mb-2 mt-3">
<div className="row g-2">
@ -561,7 +568,13 @@ class Table extends React.Component {
<div className="text">{item.name}</div>
</div>
<div className="col-auto">
<img onClick={() => this.removeColumn(index)} className="svg-icon" src="/assets/images/icons/trash.svg" width="12" height="12"/>
<img
onClick={() => this.removeColumn(index)}
className="svg-icon"
src="/assets/images/icons/trash.svg"
width="12"
height="12"
/>
</div>
</div>
</OverlayTrigger>
@ -583,21 +596,92 @@ class Table extends React.Component {
</div>
</div>
<div>{actions.value.map((action, index) => this.actionButton(action, index))}</div>
{actions.value.length === 0 &&
<center><small>This table doesn't have any action buttons</small></center>
}
{actions.value.length === 0 && (
<center>
<small>This table doesn&apos;t have any action buttons</small>
</center>
)}
</div>
<hr></hr>
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'serverSidePagination', 'properties', currentState)}
{!serverSidePagination && renderElement(component, componentMeta, paramUpdated, dataQueries, 'clientSidePagination', 'properties', currentState)}
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'displaySearchBox', 'properties', currentState)}
{displaySearchBox && renderElement(component, componentMeta, paramUpdated, dataQueries, 'serverSideSearch', 'properties', currentState)}
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'showDownloadButton', 'properties', currentState)}
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'showFilterButton', 'properties', currentState)}
{renderElement(component, componentMeta, paramUpdated, dataQueries, 'showBulkUpdateActions', 'properties', currentState)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'serverSidePagination',
'properties',
currentState
)}
{!serverSidePagination &&
renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'clientSidePagination',
'properties',
currentState
)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'displaySearchBox',
'properties',
currentState
)}
{displaySearchBox &&
renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'serverSideSearch',
'properties',
currentState
)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'showDownloadButton',
'properties',
currentState
)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'showFilterButton',
'properties',
currentState
)}
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'showBulkUpdateActions',
'properties',
currentState
)}
{Object.keys(componentMeta.styles).map((style) => renderElement(component, componentMeta, paramUpdated, dataQueries, style, 'styles', currentState, components))}
{Object.keys(componentMeta.styles).map((style) =>
renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
style,
'styles',
currentState,
components
)
)}
<div className="hr-text">Events</div>
<EventManager

View file

@ -2,17 +2,7 @@ import React from 'react';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
import { ToolTip } from './Components/ToolTip';
export const Code = ({
param,
definition,
onChange,
paramType,
dataQueries,
components,
componentMeta,
currentState,
darkMode,
}) => {
export const Code = ({ param, definition, onChange, paramType, componentMeta, currentState, darkMode }) => {
const initialValue = definition ? definition.value : '';
const paramMeta = componentMeta[paramType][param.name];
const displayName = paramMeta.displayName || param.name;

View file

@ -2,9 +2,7 @@ import React, { useState } from 'react';
import { SketchPicker } from 'react-color';
import { ToolTip } from './Components/ToolTip';
export const Color = ({
param, definition, onChange, paramType, componentMeta
}) => {
export const Color = ({ param, definition, onChange, paramType, componentMeta }) => {
const [showPicker, setShowPicker] = useState(false);
const coverStyles = {
@ -12,7 +10,7 @@ export const Color = ({
top: '0px',
right: '0px',
bottom: '0px',
left: '0px'
left: '0px',
};
const paramMeta = componentMeta[paramType][param.name] || {};
@ -20,7 +18,7 @@ export const Color = ({
return (
<div className="field mb-3">
<ToolTip label={displayName} meta={paramMeta}/>
<ToolTip label={displayName} meta={paramMeta} />
{showPicker && (
<div>
@ -34,11 +32,11 @@ export const Color = ({
)}
<div className="row mx-0 form-control color-picker-input" onClick={() => setShowPicker(true)}>
<div className="col-auto" style={{float: 'right', width: '20px', height: '20px', backgroundColor: definition.value}}>
</div>
<div className="col">
{definition.value}
</div>
<div
className="col-auto"
style={{ float: 'right', width: '20px', height: '20px', backgroundColor: definition.value }}
></div>
<div className="col">{definition.value}</div>
</div>
</div>
);

View file

@ -4,34 +4,27 @@ import Tooltip from 'react-bootstrap/Tooltip';
const tooltipStyle = {
textDecorationLine: 'underline',
textDecorationStyle: 'dashed'
}
export const ToolTip = ({
label, meta, labelClass
}) => {
textDecorationStyle: 'dashed',
};
export const ToolTip = ({ label, meta, labelClass }) => {
function renderTooltip(props) {
return <Tooltip id="button-tooltip" {...props}>
{meta.tip}
</Tooltip>
};
return (
<Tooltip id="button-tooltip" {...props}>
{meta.tip}
</Tooltip>
);
}
if (meta.tip) {
return (<OverlayTrigger
placement="left"
delay={{ show: 250, hide: 400 }}
overlay={renderTooltip}
>
<label
style={tooltipStyle}
className={labelClass || 'form-label'}
>
return (
<OverlayTrigger placement="left" delay={{ show: 250, hide: 400 }} overlay={renderTooltip}>
<label style={tooltipStyle} className={labelClass || 'form-label'}>
{label}
</label>
</OverlayTrigger>
</label>
</OverlayTrigger>
);
} else {
return (<label className={labelClass || 'form-label'}>{label}</label>);
return <label className={labelClass || 'form-label'}>{label}</label>;
}
}
};

View file

@ -3,9 +3,7 @@ import CodeMirror from '@uiw/react-codemirror';
import 'codemirror/theme/duotone-light.css';
import { ToolTip } from './Components/ToolTip';
export const Json = ({
param, definition, onChange, paramType, componentMeta
}) => {
export const Json = ({ param, definition, onChange, paramType, componentMeta }) => {
const value = definition
? definition.value
: `[{
@ -15,11 +13,11 @@ export const Json = ({
}]`;
const paramMeta = componentMeta[paramType][param.name];
const displayName = paramMeta.displayName || param.name;
const displayName = paramMeta.displayName || param.name;
return (
<div className="field mb-2">
<ToolTip label={displayName} meta={paramMeta}/>
<ToolTip label={displayName} meta={paramMeta} />
<CodeMirror
height="300px"
fontSize="2"
@ -29,7 +27,7 @@ export const Json = ({
theme: 'duotone-light',
mode: 'json',
lineWrapping: true,
scrollbarStyle: null
scrollbarStyle: null,
}}
/>
</div>

View file

@ -2,10 +2,7 @@ import React from 'react';
import { ToolTip } from './Components/ToolTip';
import SelectSearch, { fuzzySearch } from 'react-select-search';
export const Select = ({
param, definition, onChange, paramType, componentMeta
}) => {
export const Select = ({ param, definition, onChange, paramType, componentMeta }) => {
const paramMeta = componentMeta[paramType][param.name];
const displayName = paramMeta.displayName || param.name;
const options = paramMeta.options;
@ -13,7 +10,7 @@ export const Select = ({
return (
<div className="field mb-3">
<ToolTip label={displayName} meta={paramMeta}/>
<ToolTip label={displayName} meta={paramMeta} />
<SelectSearch
options={options}
value={value}
@ -21,7 +18,7 @@ export const Select = ({
onChange={(newVal) => onChange(param, 'value', newVal, paramType)}
filterOptions={fuzzySearch}
placeholder="Select.."
/>
/>
</div>
);
};

View file

@ -1,22 +1,20 @@
import React from 'react';
import { ToolTip } from './Components/ToolTip';
export const Text = ({
param, definition, onChange, paramType, componentMeta
}) => {
export const Text = ({ param, definition, onChange, paramType, componentMeta }) => {
const value = definition ? definition.value : '';
const paramMeta = componentMeta[paramType][param.name];
const displayName = paramMeta.displayName || param.name;
return (
<div className="field mb-3">
<ToolTip label={displayName} meta={paramMeta}/>
<ToolTip label={displayName} meta={paramMeta} />
<input
type="text"
onBlur={(e) => onChange(param, 'value', e.target.value, paramType)}
onKeyDown={(e) => {
if(e.key === 'Enter') {
onChange(param, 'value', e.target.value, paramType)
if (e.key === 'Enter') {
onChange(param, 'value', e.target.value, paramType);
}
}}
className="form-control text-field"

View file

@ -1,9 +1,7 @@
import React from 'react';
import { ToolTip } from './Components/ToolTip';
export const Toggle = ({
param, definition, onChange, paramType, componentMeta
}) => {
export const Toggle = ({ param, definition, onChange, paramType, componentMeta }) => {
const value = definition?.value !== false ?? false;
const paramMeta = componentMeta[paramType][param.name];
const displayName = paramMeta.displayName || param.name;
@ -11,14 +9,14 @@ export const Toggle = ({
return (
<div className="field mb-3">
<label className="form-check form-switch my-2">
<input
className="form-check-input"
type="checkbox"
onClick={() => onChange(param, 'value', !value, paramType)}
checked={value}
/>
<ToolTip label={displayName} meta={paramMeta} labelClass="form-check-label"/>
</label>
<input
className="form-check-input"
type="checkbox"
onClick={() => onChange(param, 'value', !value, paramType)}
checked={value}
/>
<ToolTip label={displayName} meta={paramMeta} labelClass="form-check-label" />
</label>
</div>
);
};
};

View file

@ -16,9 +16,8 @@ export const EventManager = ({
apps,
excludeEvents,
popOverCallback,
popoverPlacement
popoverPlacement,
}) => {
const [focusedEventIndex, setFocusedEventIndex] = useState(null);
let actionOptions = ActionTypes.map((action) => {
@ -28,32 +27,39 @@ export const EventManager = ({
excludeEvents = excludeEvents || [];
/* Filter events based on excludesEvents ( a list of event ids to exclude ) */
let possibleEvents = Object.keys(componentMeta.events).filter(eventId => !excludeEvents.includes(eventId)).map(eventId => { return {
name: componentMeta.events[eventId].displayName, value: eventId
}})
let possibleEvents = Object.keys(componentMeta.events)
.filter((eventId) => !excludeEvents.includes(eventId))
.map((eventId) => {
return {
name: componentMeta.events[eventId].displayName,
value: eventId,
};
});
function getModalOptions() {
let modalOptions = [];
Object.keys(components || {}).forEach((key) => {
if(components[key].component.component === 'Modal') {
if (components[key].component.component === 'Modal') {
modalOptions.push({
name: components[key].component.name,
value: key
})
value: key,
});
}
})
});
return modalOptions;
}
function getAllApps() {
let appsOptionsList = [];
apps.filter(item => item.slug != undefined).map((item) => {
appsOptionsList.push({
name: item.name,
value: item.slug
})
})
apps
.filter((item) => item.slug != undefined)
.map((item) => {
appsOptionsList.push({
name: item.name,
value: item.slug,
});
});
return appsOptionsList;
}
@ -79,7 +85,7 @@ export const EventManager = ({
newEvents.push({
eventId: Object.keys(componentMeta.events)[0],
actionId: 'show-alert',
message: 'Hello world!'
message: 'Hello world!',
});
eventsChanged(newEvents);
}
@ -90,9 +96,7 @@ export const EventManager = ({
<Popover.Content>
<div className="row">
<div className="col-3 p-2">
<span>
Event
</span>
<span>Event</span>
</div>
<div className="col-9">
<SelectSearch
@ -107,9 +111,7 @@ export const EventManager = ({
</div>
<div className="row mt-3">
<div className="col-3 p-2">
<span>
Action
</span>
<span>Action</span>
</div>
<div className="col-9">
<SelectSearch
@ -124,192 +126,179 @@ export const EventManager = ({
</div>
<div className="hr-text">Action options</div>
<div>
{event.actionId === 'show-alert' && (
<div className="row">
<div className="col-3 p-2">
Message
</div>
<div className="col-9">
<div>
{event.actionId === 'show-alert' && (
<div className="row">
<div className="col-3 p-2">Message</div>
<div className="col-9">
<CodeHinter
currentState={currentState}
initialValue={event.message}
onChange={(value) => handlerChanged(index, 'message', value)}
/>
</div>
</div>
)}
</div>
)}
{event.actionId === 'open-webpage' && (
<div className="p-1">
<label className="form-label mt-1">URL</label>
<CodeHinter
currentState={currentState}
initialValue={event.url}
onChange={(value) => handlerChanged(index, 'url', value)}
/>
</div>
)}
{event.actionId === 'go-to-app' &&
<GotoApp
event={event}
handlerChanged={handlerChanged}
eventIndex={index}
getAllApps={getAllApps}
{event.actionId === 'open-webpage' && (
<div className="p-1">
<label className="form-label mt-1">URL</label>
<CodeHinter
currentState={currentState}
initialValue={event.url}
onChange={(value) => handlerChanged(index, 'url', value)}
/>
}
</div>
)}
{event.actionId === 'show-modal' && (
<div className="row">
<div className="col-3 p-2">
Modal
</div>
<div className="col-9">
<SelectSearch
options={getModalOptions()}
value={event.model}
search={true}
onChange={(value) => {
handlerChanged(index, 'modal', value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
/>
</div>
</div>
)}
{event.actionId === 'go-to-app' && (
<GotoApp
event={event}
handlerChanged={handlerChanged}
eventIndex={index}
getAllApps={getAllApps}
currentState={currentState}
/>
)}
{event.actionId === 'close-modal' && (
<div className="row">
<div className="col-3 p-2">
Modal
</div>
<div className="col-9">
<SelectSearch
options={getModalOptions()}
value={event.model}
search={true}
onChange={(value) => {
handlerChanged(index, 'modal', value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
/>
</div>
</div>
)}
{event.actionId === 'copy-to-clipboard' && (
<div className="p-1">
<label className="form-label mt-1">Text</label>
<CodeHinter
currentState={currentState}
onChange={(value) => handlerChanged(index, 'contentToCopy', value)}
{event.actionId === 'show-modal' && (
<div className="row">
<div className="col-3 p-2">Modal</div>
<div className="col-9">
<SelectSearch
options={getModalOptions()}
value={event.model}
search={true}
onChange={(value) => {
handlerChanged(index, 'modal', value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
/>
</div>
)}
</div>
)}
{event.actionId === 'run-query' && (
<div className="row">
<div className="col-3 p-2">
Query
</div>
<div className="col-9">
<SelectSearch
options={dataQueries.map((query) => {
return { name: query.name, value: query.id };
})}
value={event.queryId}
search={true}
onChange={(value) => {
const query = dataQueries.find((dataquery) => dataquery.id === value);
handlerChanged(index, 'queryId', query.id);
handlerChanged(index, 'queryName', query.name);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
/>
</div>
{event.actionId === 'close-modal' && (
<div className="row">
<div className="col-3 p-2">Modal</div>
<div className="col-9">
<SelectSearch
options={getModalOptions()}
value={event.model}
search={true}
onChange={(value) => {
handlerChanged(index, 'modal', value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
/>
</div>
)}
</div>
</div>
)}
{event.actionId === 'copy-to-clipboard' && (
<div className="p-1">
<label className="form-label mt-1">Text</label>
<CodeHinter
currentState={currentState}
onChange={(value) => handlerChanged(index, 'contentToCopy', value)}
/>
</div>
)}
{event.actionId === 'run-query' && (
<div className="row">
<div className="col-3 p-2">Query</div>
<div className="col-9">
<SelectSearch
options={dataQueries.map((query) => {
return { name: query.name, value: query.id };
})}
value={event.queryId}
search={true}
onChange={(value) => {
const query = dataQueries.find((dataquery) => dataquery.id === value);
handlerChanged(index, 'queryId', query.id);
handlerChanged(index, 'queryName', query.name);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
/>
</div>
</div>
)}
</div>
</Popover.Content>
</Popover>
)
);
}
function renderHandlers(events) {
return events.map((event, index) => {
const actionMeta = ActionTypes.find((action) => action.id === event.actionId);
const rowClassName = `row g-0 border-bottom pb-2 pt-2 px-2 ${focusedEventIndex === index ? ' bg-azure-lt' : ''}`;
return <div>
<OverlayTrigger
trigger="click"
placement={popoverPlacement || 'left'}
rootClose={true}
overlay={eventPopover(event, index)}
onHide={(e) => setFocusedEventIndex(null) }
onToggle={ (showing) => {
if(showing) {
setFocusedEventIndex(index);
} else {
setFocusedEventIndex(null);
}
if (typeof popOverCallback === 'function')
popOverCallback(showing);
}}
>
<div className={rowClassName} role="button">
<div className="col">
{componentMeta.events[event.eventId]['displayName']}
return (
<div key={index}>
<OverlayTrigger
trigger="click"
placement={popoverPlacement || 'left'}
rootClose={true}
overlay={eventPopover(event, index)}
onHide={() => setFocusedEventIndex(null)}
onToggle={(showing) => {
if (showing) {
setFocusedEventIndex(index);
} else {
setFocusedEventIndex(null);
}
if (typeof popOverCallback === 'function') popOverCallback(showing);
}}
>
<div className={rowClassName} role="button">
<div className="col">{componentMeta.events[event.eventId]['displayName']}</div>
<div className="col">
<small className="text-muted">{actionMeta.name}</small>
</div>
<div className="col-auto">
<span
className="text-danger"
onClick={(e) => {
e.stopPropagation();
removeHandler(index);
}}
>
<img className="svg-icon" src="/assets/images/icons/trash.svg" width="12" height="12" />
</span>
</div>
</div>
<div className="col">
<small className="text-muted">{actionMeta.name}</small>
</div>
<div className="col-auto">
<span className="text-danger" onClick={(e) => { e.stopPropagation(); removeHandler(index) }}>
<img className="svg-icon" src="/assets/images/icons/trash.svg" width="12" height="12" />
</span>
</div>
</div>
</OverlayTrigger>
</div>
})
</OverlayTrigger>
</div>
);
});
}
const events = component.component.definition.events || [];
if(events.length === 0) {
return <div>
<center>
<button
className="btn btn-sm btn-outline-azure"
onClick={addHandler}
>
Add event handler
</button>
</center>
</div>
if (events.length === 0) {
return (
<div>
<center>
<button className="btn btn-sm btn-outline-azure" onClick={addHandler}>
Add event handler
</button>
</center>
</div>
);
}
return <div className="card">
<div className="card-body p-0">
{renderHandlers(events)}
</div>
<button
className="btn btn-sm btn-outline-azure"
onClick={addHandler}
>
return (
<div className="card">
<div className="card-body p-0">{renderHandlers(events)}</div>
<button className="btn btn-sm btn-outline-azure" onClick={addHandler}>
Add handler
</button>
</div>
}
</button>
</div>
);
};

View file

@ -11,24 +11,27 @@ export const Inspector = ({
selectedComponentId,
componentDefinitionChanged,
dataQueries,
removeComponent,
allComponents,
componentChanged,
currentState,
apps,
darkMode,
switchSidebarTab
switchSidebarTab,
}) => {
const selectedComponent = { id: selectedComponentId, component: allComponents[selectedComponentId].component, layouts: allComponents[selectedComponentId].layouts}
const selectedComponent = {
id: selectedComponentId,
component: allComponents[selectedComponentId].component,
layouts: allComponents[selectedComponentId].layouts,
};
const [component, setComponent] = useState(selectedComponent);
const [components, setComponents] = useState(allComponents);
const componentMeta = componentTypes.find((comp) => component.component.component === comp.component);
useEffect(() => {
setComponent(selectedComponent);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedComponent.component.definition]);
useEffect(() => {
@ -42,7 +45,9 @@ export const Inspector = ({
setComponent(newComponent);
componentChanged(newComponent);
} else {
toast.error('Invalid query name. Should be unique and only include letters, numbers and underscore.', { hideProgressBar: true });
toast.error('Invalid query name. Should be unique and only include letters, numbers and underscore.', {
hideProgressBar: true,
});
}
}
@ -56,7 +61,7 @@ export const Inspector = ({
allParams[param.name] = {};
}
if(attr) {
if (attr) {
allParams[param.name][attr] = value;
} else {
allParams[param.name] = value;
@ -68,65 +73,61 @@ export const Inspector = ({
...component,
component: {
...component.component,
definition: newDefinition
}
definition: newDefinition,
},
};
setComponent(newComponent);
componentDefinitionChanged(newComponent);
}
function layoutPropertyChanged(param, attr, value, paramType) {
function layoutPropertyChanged(param, attr, value, paramType) {
paramUpdated(param, attr, value, paramType);
// User wants to show the widget on mobile devices
if(param.name === 'showOnMobile' && value === true) {
if (param.name === 'showOnMobile' && value === true) {
let newComponent = {
...component
...component,
};
const { width, height } = newComponent.layouts['desktop'];
newComponent['layouts'] = {
...newComponent.layouts,
mobile: {
top: 100,
left: 0,
width: Math.min(width, 445),
height: height
}
}
mobile: {
top: 100,
left: 0,
width: Math.min(width, 445),
height: height,
},
};
setComponent(newComponent);
componentDefinitionChanged(newComponent).then(() => {
// Child componets should also have a mobile layout
const childComponents = Object.keys(allComponents).filter((key) => allComponents[key].parent === component.id);
childComponents.forEach((componentId) => {
let newChild = {
id: componentId,
...allComponents[componentId]
...allComponents[componentId],
};
const { width, height } = newChild.layouts['desktop'];
newChild['layouts'] = {
...newChild.layouts,
mobile: {
top: 100,
left: 0,
width: Math.min(width, 445),
height: height
}
}
mobile: {
top: 100,
left: 0,
width: Math.min(width, 445),
height: height,
},
};
componentDefinitionChanged(newChild);
});
});
}
}
@ -135,7 +136,7 @@ export const Inspector = ({
newDefinition.events[event.name] = { actionId };
let newComponent = {
...component
...component,
};
setComponent(newComponent);
@ -147,7 +148,7 @@ export const Inspector = ({
newDefinition.events = newEvents;
let newComponent = {
...component
...component,
};
setComponent(newComponent);
@ -163,7 +164,7 @@ export const Inspector = ({
newDefinition.events[event.name] = { ...eventDefinition, options: { ...eventDefinition.options, [option]: value } };
let newComponent = {
...component
...component,
};
setComponent(newComponent);
@ -174,30 +175,26 @@ export const Inspector = ({
<div className="inspector">
<div className="header px-2 py-1 row">
<div className="col-auto">
<div className="input-icon">
<input
type="text"
onChange={(e) => handleComponentNameChange(e.target.value)}
className="form-control-plaintext form-control-plaintext-sm mt-1"
value={component.component.name}
/>
<span className="input-icon-addon">
<img src="/assets/images/icons/edit-source.svg" width="12" height="12" />
</span>
</div>
<div className="input-icon">
<input
type="text"
onChange={(e) => handleComponentNameChange(e.target.value)}
className="form-control-plaintext form-control-plaintext-sm mt-1"
value={component.component.name}
/>
<span className="input-icon-addon">
<img src="/assets/images/icons/edit-source.svg" width="12" height="12" />
</span>
</div>
</div>
<div className="col py-1">
<button
className="btn btn-sm component-action-button btn-light"
onClick={() => switchSidebarTab(2)}
>
<button className="btn btn-sm component-action-button btn-light" onClick={() => switchSidebarTab(2)}>
x
</button>
</div>
</div>
{componentMeta.component === 'Table' &&
{componentMeta.component === 'Table' && (
<Table
component={component}
paramUpdated={paramUpdated}
@ -211,9 +208,9 @@ export const Inspector = ({
eventsChanged={eventsChanged}
apps={apps}
/>
}
)}
{componentMeta.component === 'Chart' &&
{componentMeta.component === 'Chart' && (
<Chart
component={component}
paramUpdated={paramUpdated}
@ -225,16 +222,39 @@ export const Inspector = ({
currentState={currentState}
darkMode={darkMode}
/>
}
{!['Table', 'Chart'].includes(componentMeta.component) &&
<div className="properties-container p-2">
{Object.keys(componentMeta.properties).map((property) => renderElement(component, componentMeta, paramUpdated, dataQueries, property, 'properties', currentState, components, darkMode))}
{Object.keys(componentMeta.styles).length > 0 && <div className="hr-text">Style</div>}
{Object.keys(componentMeta.styles).map((style) => renderElement(component, componentMeta, paramUpdated, dataQueries, style, 'styles', currentState, components))}
)}
{Object.keys(componentMeta.events).length > 0 &&
{!['Table', 'Chart'].includes(componentMeta.component) && (
<div className="properties-container p-2">
{Object.keys(componentMeta.properties).map((property) =>
renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
property,
'properties',
currentState,
components,
darkMode
)
)}
{Object.keys(componentMeta.styles).length > 0 && <div className="hr-text">Style</div>}
{Object.keys(componentMeta.styles).map((style) =>
renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
style,
'styles',
currentState,
components
)
)}
{Object.keys(componentMeta.events).length > 0 && (
<div>
{Object.keys(componentMeta.events).length > 0 && <div className="hr-text">Events</div>}
@ -248,31 +268,61 @@ export const Inspector = ({
apps={apps}
/>
</div>
}
)}
{Object.keys(componentMeta.validation || {}).length > 0 &&
{Object.keys(componentMeta.validation || {}).length > 0 && (
<div>
<div className="hr-text">Validation</div>
{Object.keys(componentMeta.validation).map((property) => renderElement(component, componentMeta, paramUpdated, dataQueries, property, 'validation', currentState, components, darkMode))}
{Object.keys(componentMeta.validation).map((property) =>
renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
property,
'validation',
currentState,
components,
darkMode
)
)}
</div>
}
)}
</div>
}
)}
{/* Show on desktop & show on mobile params */}
<div className="hr-text">Layout</div>
<div className="properties-container p-2 pb-3 mb-5">
{renderElement(component, componentMeta, layoutPropertyChanged, dataQueries, 'showOnDesktop', 'others', currentState, components)}
{renderElement(component, componentMeta, layoutPropertyChanged, dataQueries, 'showOnMobile', 'others', currentState, components)}
{renderElement(
component,
componentMeta,
layoutPropertyChanged,
dataQueries,
'showOnDesktop',
'others',
currentState,
components
)}
{renderElement(
component,
componentMeta,
layoutPropertyChanged,
dataQueries,
'showOnMobile',
'others',
currentState,
components
)}
</div>
<div className="widget-documentation-link p-2">
<a href={`https://docs.tooljet.io/docs/widgets/${convertToKebabCase(componentMeta?.name ?? '')}`} target="_blank">
<small>
{componentMeta.name} documentation
</small>
<a
href={`https://docs.tooljet.io/docs/widgets/${convertToKebabCase(componentMeta?.name ?? '')}`}
target="_blank"
rel="noreferrer"
>
<small>{componentMeta.name} documentation</small>
</a>
</div>
</div>

View file

@ -1,19 +1,8 @@
import React, { useState } from 'react';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import Collapse from 'react-bootstrap/Collapse'
export const QuerySelector = ({
param,
definition,
eventUpdated,
eventOptionUpdated,
dataQueries,
extraData,
eventMeta,
currentState,
components
}) => {
import Collapse from 'react-bootstrap/Collapse';
export const QuerySelector = ({ param, definition, eventOptionUpdated, dataQueries, extraData, eventMeta }) => {
const [open, setOpen] = useState(false);
function onChange(value) {
@ -34,9 +23,7 @@ export const QuerySelector = ({
<div className="field mb-3 mt-1 px-2">
<label className="form-label" role="button" onClick={() => setOpen(!open)}>
<div className="row">
<div className="col">
{eventMeta.displayName}
</div>
<div className="col">{eventMeta.displayName}</div>
<div className={`col-auto events-toggle ${open ? 'events-toggle-active' : ''}`}>
<span className="toggle-icon"></span>
</div>

View file

@ -5,5 +5,5 @@ export const TypeMapping = {
json: 'Json',
code: 'Code',
toggle: 'Toggle',
select: 'Select'
select: 'Select',
};

View file

@ -1,3 +1,3 @@
export const ItemTypes = {
BOX: 'box'
BOX: 'box',
};

View file

@ -6,193 +6,207 @@ import _ from 'lodash';
import moment from 'moment';
import { SidebarPinnedButton } from './SidebarPinnedButton';
export const LeftSidebarDebugger = ({ darkMode, errors }) => {
const [open, trigger, content, popoverPinned, updatePopoverPinnedState] = usePinnedPopover(false);
const [currrentTab, setCurrentTab] = React.useState(1);
const [errorLogs, setErrorLogs] = React.useState([]);
const [unReadErrorCount, setUnReadErrorCount] = React.useState({ read: 0, unread: 0 });
export const LeftSidebarDebugger = ({ darkMode, components, errors }) => {
const [open, trigger, content, popoverPinned, updatePopoverPinnedState] = usePinnedPopover(false)
const [currrentTab, setCurrentTab] = React.useState(1)
const [errorLogs, setErrorLogs] = React.useState([])
const [unReadErrorCount, setUnReadErrorCount] = React.useState({read: 0, unread: 0})
const switchCurrentTab = (tab) => {
setCurrentTab(tab);
};
const switchCurrentTab = (tab) => {
setCurrentTab(tab)
}
const clearErrorLogs = () => {
setUnReadErrorCount(() => {
return { read: 0, unread: 0 };
});
const clearErrorLogs = () => {
setUnReadErrorCount(() => {
return { read: 0, unread: 0 }
})
setErrorLogs(() => []);
};
setErrorLogs(() => [])
}
React.useEffect(() => {
setErrorLogs((prev) => {
let copy = JSON.parse(JSON.stringify(prev));
copy = copy.filter((val) => Object.keys(val).length !== 0);
const newError = _.flow([
Object.entries,
// eslint-disable-next-line no-unused-vars
(arr) => arr.filter(([key, value]) => value.data.status),
Object.fromEntries,
])(errors);
React.useEffect(() => {
setErrorLogs(prev => {
let copy = JSON.parse(JSON.stringify(prev))
copy = copy.filter(val => Object.keys(val).length !== 0)
const newError = _.flow([
Object.entries,
arr => arr.filter(([key, value]) => value.data.status),
Object.fromEntries
])(errors);
const errorData = []
Object.entries(newError).map(([key, value]) => {
const variableNames = {
options: '',
response: ''
}
switch (value.type) {
case 'query':
variableNames.options = 'substitutedVariables';
variableNames.response = 'response';
break;
default: 'options';
}
errorData.push({
type: value.type,
key: key,
message: value.data.message,
description: value.data.description,
options: {name: variableNames.options, data: value.options},
response: {name: variableNames.response, data: value.data.data},
timestamp: moment()
})
})
const newData = [...errorData, ...copy]
return newData
})
},[errors])
React.useEffect(() => {
if(open === false && errorLogs.length !== unReadErrorCount.read) {
const unReadErrors = errorLogs.length - unReadErrorCount.read
setUnReadErrorCount((prev) => {
let copy = JSON.parse(JSON.stringify(prev))
copy.unread = unReadErrors
return copy
})
if(popoverPinned) {
setTimeout(() => {
setUnReadErrorCount((prev) => {
let copy = JSON.parse(JSON.stringify(prev))
copy.read = errorLogs.length
copy.unread = 0
return copy
})
}, 900);
}
} else {
setUnReadErrorCount((prev) => {
let copy = JSON.parse(JSON.stringify(prev))
copy.read = errorLogs.length
copy.unread = 0
return copy
})
const errorData = [];
Object.entries(newError).map(([key, value]) => {
const variableNames = {
options: '',
response: '',
};
switch (value.type) {
case 'query':
variableNames.options = 'substitutedVariables';
variableNames.response = 'response';
break;
default:
'options';
}
},[errorLogs.length, open])
errorData.push({
type: value.type,
key: key,
message: value.data.message,
description: value.data.description,
options: { name: variableNames.options, data: value.options },
response: { name: variableNames.response, data: value.data.data },
timestamp: moment(),
});
});
return (
const newData = [...errorData, ...copy];
return newData;
});
}, [errors]);
React.useEffect(() => {
if (open === false && errorLogs.length !== unReadErrorCount.read) {
const unReadErrors = errorLogs.length - unReadErrorCount.read;
setUnReadErrorCount((prev) => {
let copy = JSON.parse(JSON.stringify(prev));
copy.unread = unReadErrors;
return copy;
});
if (popoverPinned) {
setTimeout(() => {
setUnReadErrorCount((prev) => {
let copy = JSON.parse(JSON.stringify(prev));
copy.read = errorLogs.length;
copy.unread = 0;
return copy;
});
}, 900);
}
} else {
setUnReadErrorCount((prev) => {
let copy = JSON.parse(JSON.stringify(prev));
copy.read = errorLogs.length;
copy.unread = 0;
return copy;
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [errorLogs.length, open]);
return (
<>
<LeftSidebarItem tip='Debugger' {...trigger} icon='debugger' className={`left-sidebar-item ${open && 'active'}`} badge={true} count={unReadErrorCount.unread} />
<div {...content} className={`card popover debugger-popover ${open || popoverPinned ? 'show' : 'hide'}`} style={{minWidth:'180px', minHeight:'108px', maxWidth:'480px'}} >
<div className="row-header">
<div className="nav-header">
<ul className="nav nav-tabs d-flex justify-content-between" data-bs-toggle="tabs">
<li className="nav-item">
<a onClick={() => switchCurrentTab(1)} className={currrentTab === 1 ? "nav-link active" : "nav-link"}>
Errors
</a>
</li>
<li className="btn-group">
{errorLogs.length > 0 && (
<button onClick={clearErrorLogs} type="button" className="btn btn-light btn-sm m-1 py-1" aria-label="clear button">
<span className="text-muted">clear</span>
</button>
)}
<SidebarPinnedButton component={'Debugger'} state={popoverPinned} updateState={updatePopoverPinnedState} />
</li>
</ul>
</div>
</div>
{currrentTab === 1 && (
<div className="card-body">
{errorLogs.length === 0 && (
<center className="p-2 text-muted">
No errors found.
</center>
<LeftSidebarItem
tip="Debugger"
{...trigger}
icon="debugger"
className={`left-sidebar-item ${open && 'active'}`}
badge={true}
count={unReadErrorCount.unread}
/>
<div
{...content}
className={`card popover debugger-popover ${open || popoverPinned ? 'show' : 'hide'}`}
style={{ minWidth: '180px', minHeight: '108px', maxWidth: '480px' }}
>
<div className="row-header">
<div className="nav-header">
<ul className="nav nav-tabs d-flex justify-content-between" data-bs-toggle="tabs">
<li className="nav-item">
<a onClick={() => switchCurrentTab(1)} className={currrentTab === 1 ? 'nav-link active' : 'nav-link'}>
Errors
</a>
</li>
<li className="btn-group">
{errorLogs.length > 0 && (
<button
onClick={clearErrorLogs}
type="button"
className="btn btn-light btn-sm m-1 py-1"
aria-label="clear button"
>
<span className="text-muted">clear</span>
</button>
)}
<SidebarPinnedButton
component={'Debugger'}
state={popoverPinned}
updateState={updatePopoverPinnedState}
/>
</li>
</ul>
</div>
</div>
<div className="tab-content">
{errorLogs.map((error, index) => (
<LeftSidebarDebugger.ErrorLogs errorProps={error} idx={index} darkMode={darkMode} />
))}
</div>
{currrentTab === 1 && (
<div className="card-body">
{errorLogs.length === 0 && <center className="p-2 text-muted">No errors found.</center>}
<div className="tab-content">
{errorLogs.map((error, index) => (
<LeftSidebarDebugger.ErrorLogs key={index} errorProps={error} idx={index} darkMode={darkMode} />
))}
</div>
</div>
)}
</div>
</>
)
);
};
function ErrorLogsComponent({ errorProps, idx, darkMode }) {
const [open, setOpen] = React.useState(false);
return (
<div className="tab-content" key={`${errorProps.key}-${idx}`}>
<p className="text-azure" onClick={() => setOpen((prev) => !prev)}>
<img
className={`svg-icon ${open ? 'iopen' : ''}`}
src={`/assets/images/icons/caret-right.svg`}
width="16"
height="16"
/>
[{_.capitalize(errorProps.type)} {errorProps.key}] &nbsp;
<span className="text-red">
{`Query Failed: ${errorProps.description}`} {errorProps.message}.
</span>
<br />
<small className="text-muted px-1">{moment(errorProps.timestamp).fromNow()}</small>
</p>
<div className={` queryData ${open ? 'open' : 'close'} py-0`}>
<span>
<ReactJson
src={errorProps.options.data}
theme={darkMode ? 'shapeshifter' : 'rjv-default'}
name={errorProps.options.name}
style={{ fontSize: '0.7rem', paddingLeft: '0.17rem' }}
enableClipboard={false}
displayDataTypes={false}
collapsed={true}
displayObjectSize={false}
quotesOnKeys={false}
sortKeys={false}
/>
</span>
<span>
<ReactJson
src={errorProps.response.data}
theme={darkMode ? 'shapeshifter' : 'rjv-default'}
name={errorProps.response.name}
style={{ fontSize: '0.7rem', paddingLeft: '0.17rem' }}
enableClipboard={false}
displayDataTypes={false}
collapsed={true}
displayObjectSize={false}
quotesOnKeys={false}
sortKeys={false}
/>
</span>
<hr className="border-1 border-bottom bg-grey py-0" />
</div>
</div>
);
}
function ErrorLogsComponent ({ errorProps, idx, darkMode }) {
const [open, setOpen] = React.useState(false)
return (
<div className="tab-content" key={`${errorProps.key}-${idx}`}>
<p className='text-azure' onClick={() => setOpen((prev) => !prev)}>
<img className={`svg-icon ${open ? 'iopen': ''}`} src={`/assets/images/icons/caret-right.svg`} width="16" height="16"/>
[{_.capitalize(errorProps.type)} {errorProps.key}] &nbsp;
<span className="text-red">{`Query Failed: ${errorProps.description}`} {errorProps.message}.</span>
<br />
<small className="text-muted px-1">{moment(errorProps.timestamp).fromNow()}</small>
</p>
<div className={` queryData ${open ? 'open' : 'close'} py-0`} >
<span>
<ReactJson
src={errorProps.options.data}
theme={darkMode ? 'shapeshifter' : 'rjv-default'}
name={errorProps.options.name}
style={{ fontSize: '0.7rem', paddingLeft:'0.17rem' }}
enableClipboard={false}
displayDataTypes={false}
collapsed={true}
displayObjectSize={false}
quotesOnKeys={false}
sortKeys={false}
/>
</span>
<span>
<ReactJson
src={errorProps.response.data}
theme={darkMode ? 'shapeshifter' : 'rjv-default'}
name={errorProps.response.name}
style={{ fontSize: '0.7rem', paddingLeft:'0.17rem' }}
enableClipboard={false}
displayDataTypes={false}
collapsed={true}
displayObjectSize={false}
quotesOnKeys={false}
sortKeys={false}
/>
</span>
<hr className="border-1 border-bottom bg-grey py-0" />
</div>
</div>
)
}
LeftSidebarDebugger.ErrorLogs = ErrorLogsComponent;
LeftSidebarDebugger.ErrorLogs = ErrorLogsComponent;

View file

@ -1,34 +1,36 @@
import React from 'react'
import React from 'react';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
export const SidebarPinnedButton = ({ state, component, updateState }) => {
const tooltipMsg = state ? `Unpin ${component}` : `Pin ${component}`
return (
<SidebarPinnedButton.OverlayContainer tip={tooltipMsg}>
<div className={`btn btn-sm m-1 ${state ? 'btn-light' : 'btn-default'} ${(component === 'Inspector') && "position-absolute end-0" }`} onClick={updateState} >
<img className="svg-icon" src={`/assets/images/icons/editor/left-sidebar/pinned.svg`} width="16" height="16" />
</div>
</SidebarPinnedButton.OverlayContainer>
)
}
const tooltipMsg = state ? `Unpin ${component}` : `Pin ${component}`;
return (
<SidebarPinnedButton.OverlayContainer tip={tooltipMsg}>
<div
className={`btn btn-sm m-1 ${state ? 'btn-light' : 'btn-outline-secondary'} ${
component === 'Inspector' && 'position-absolute end-0'
}`}
onClick={updateState}
>
<img className="svg-icon" src={`/assets/images/icons/editor/left-sidebar/pinned.svg`} width="16" height="16" />
</div>
</SidebarPinnedButton.OverlayContainer>
);
};
function OverlayContainer({ children, tip }) {
return (
<>
<OverlayTrigger
trigger={['click','hover', 'focus']}
placement="top"
delay={{ show: 800, hide: 100 }}
overlay={<Tooltip id="button-tooltip">{tip}</Tooltip>}
>
{ children }
</OverlayTrigger>
</>
)
}
SidebarPinnedButton.OverlayContainer = OverlayContainer
return (
<>
<OverlayTrigger
trigger={['click', 'hover', 'focus']}
placement="top"
delay={{ show: 800, hide: 100 }}
overlay={<Tooltip id="button-tooltip">{tip}</Tooltip>}
>
{children}
</OverlayTrigger>
</>
);
}
SidebarPinnedButton.OverlayContainer = OverlayContainer;

View file

@ -2,7 +2,7 @@ import React from 'react';
import usePopover from '../../_hooks/use-popover';
import { LeftSidebarItem } from './sidebar-item';
import { DataSourceManager } from '../DataSourceManager';
import { DataSourceTypes } from '../DataSourceManager/DataSourceTypes';
import { DataSourceTypes } from '../DataSourceManager/SourceComponents';
export const LeftSidebarDataSources = ({ appId, darkMode, dataSources = [], dataSourcesChanged }) => {
const [open, trigger, content] = usePopover(false);
@ -68,7 +68,6 @@ export const LeftSidebarDataSources = ({ appId, darkMode, dataSources = [], data
toggleDataSourceManagerModal(false);
}}
dataSourcesChanged={dataSourcesChanged}
showDataSourceManagerModal={showDataSourceManagerModal}
selectedDataSource={selectedDataSource}
/>
</>

View file

@ -1,18 +1,23 @@
import React from 'react';
import usePinnedPopover from '@/_hooks/usePinnedPopover';
import { LeftSidebarItem } from './sidebar-item';
import { SidebarPinnedButton} from './SidebarPinnedButton';
import { SidebarPinnedButton } from './SidebarPinnedButton';
import ReactJson from 'react-json-view';
export const LeftSidebarInspector = ({ darkMode, globals, components, queries }) => {
const [open, trigger, content, popoverPinned, updatePopoverPinnedState] = usePinnedPopover(false)
const [open, trigger, content, popoverPinned, updatePopoverPinnedState] = usePinnedPopover(false);
return (
<>
<LeftSidebarItem tip='Inspector' {...trigger} icon='inspector' className={`left-sidebar-item ${open && 'active'}`} />
<LeftSidebarItem
tip="Inspector"
{...trigger}
icon="inspector"
className={`left-sidebar-item ${open && 'active'}`}
/>
<div {...content} className={`card popover ${open || popoverPinned ? 'show' : 'hide'}`}>
<SidebarPinnedButton component={'Inspector'} state={popoverPinned} updateState={updatePopoverPinnedState} />
<div style={{marginTop:'1rem'}} className="card-body">
<div style={{ marginTop: '1rem' }} className="card-body">
<ReactJson
src={queries}
theme={darkMode ? 'shapeshifter' : 'rjv-default'}
@ -49,10 +54,10 @@ export const LeftSidebarInspector = ({ darkMode, globals, components, queries })
displayObjectSize={false}
quotesOnKeys={false}
sortKeys={true}
// indentWidth={1}
// indentWidth={1}
/>
</div>
</div>
</>
)
}
);
};

View file

@ -2,41 +2,41 @@ import React from 'react';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
export const LeftSidebarItem = ({ tip = '', className, icon, text, onClick, badge=false, count,...rest }) => {
export const LeftSidebarItem = ({ tip = '', className, icon, text, onClick, badge = false, count, ...rest }) => {
return (
<OverlayTrigger
trigger={['click','hover', 'focus']}
trigger={['click', 'hover', 'focus']}
placement="right"
delay={{ show: 800, hide: 100 }}
overlay={<Tooltip id="button-tooltip">
{tip}
</Tooltip>}
overlay={<Tooltip id="button-tooltip">{tip}</Tooltip>}
>
<div {...rest} className={className} onClick={onClick && onClick}>
{icon && <img className="svg-icon" src={`/assets/images/icons/editor/left-sidebar/${icon}.svg`} width="20" height="20" />}
{badge && <LeftSidebarItem.Badge count={count} /> }
{text && text}
{icon && (
<img
className="svg-icon"
src={`/assets/images/icons/editor/left-sidebar/${icon}.svg`}
width="20"
height="20"
/>
)}
{badge && <LeftSidebarItem.Badge count={count} />}
{text && text}
</div>
</OverlayTrigger>
)
}
);
};
function NotificationBadge({count}) {
const fontSize = count > 999 ? '7.5px' : '8.5px'
function NotificationBadge({ count }) {
const fontSize = count > 999 ? '7.5px' : '8.5px';
return (
<>
{count > 0 && (
<span
class="badge bg-red rounded-circle debugger-badge p-0"
style={{fontSize: fontSize}}
>
<span className="badge bg-red rounded-circle debugger-badge p-0" style={{ fontSize: fontSize }}>
{count > 999 ? `999+` : count}
</span>
)}
</>
)
);
}
LeftSidebarItem.Badge = NotificationBadge
LeftSidebarItem.Badge = NotificationBadge;

Some files were not shown because too many files have changed in this diff Show more