ToolJet/frontend/src/_helpers/appUtils.js

1059 lines
32 KiB
JavaScript
Raw Normal View History

2021-05-28 12:31:13 +00:00
import React from 'react';
2021-12-30 11:57:02 +00:00
import { toast } from 'react-hot-toast';
import {
getDynamicVariables,
resolveReferences,
executeMultilineJS,
serializeNestedObjectToQueryParams,
computeComponentName,
} from '@/_helpers/utils';
import { dataqueryService } from '@/_services';
2021-05-13 09:54:58 +00:00
import _ from 'lodash';
import moment from 'moment';
2021-05-28 12:31:13 +00:00
import Tooltip from 'react-bootstrap/Tooltip';
import { componentTypes } from '@/Editor/WidgetManager/components';
2021-11-24 09:33:28 +00:00
import generateCSV from '@/_lib/generate-csv';
import generateFile from '@/_lib/generate-file';
import { v4 as uuidv4 } from 'uuid';
// eslint-disable-next-line import/no-unresolved
import { allSvgs } from '@tooljet/plugins/client';
export function setStateAsync(_ref, state) {
return new Promise((resolve) => {
_ref.setState(state, resolve);
});
}
export function setCurrentStateAsync(_ref, changes) {
return new Promise((resolve) => {
_ref.setState((prevState) => {
return {
currentState: prevState.currentState,
...changes,
};
}, resolve);
});
}
export function onComponentOptionsChanged(_ref, component, options) {
const componentName = component.name;
const components = _ref.state.currentState.components;
let componentData = components[componentName];
componentData = componentData || {};
for (const option of options) {
componentData[option[0]] = option[1];
}
return setCurrentStateAsync(_ref, {
components: { ...components, [componentName]: componentData },
});
}
export function onComponentOptionChanged(_ref, component, option_name, value) {
const componentName = component.name;
const components = _ref.state.currentState.components;
let componentData = components[componentName];
componentData = componentData || {};
componentData[option_name] = value;
return setCurrentStateAsync(_ref, { components: { ...components, [componentName]: componentData } });
}
export function fetchOAuthToken(authUrl, dataSourceId) {
localStorage.setItem('sourceWaitingForOAuth', dataSourceId);
window.open(authUrl);
}
export function addToLocalStorage(object) {
localStorage.setItem(object['key'], object['value']);
}
export function getDataFromLocalStorage(key) {
return localStorage.getItem(key);
}
2021-09-08 13:51:38 +00:00
export function runTransformation(_ref, rawData, transformation, query) {
const data = rawData;
const evalFunction = Function(
['data', 'moment', '_', 'components', 'queries', 'globals', 'variables'],
transformation
);
let result = [];
const currentState = _ref.state.currentState || {};
try {
result = evalFunction(
data,
moment,
_,
currentState.components,
currentState.queries,
currentState.globals,
currentState.variables
);
} catch (err) {
console.log('Transformation failed for query: ', query.name, err);
result = { message: err.stack.split('\n')[0], status: 'failed', data: data };
}
return result;
}
export async function executeActionsForEventId(_ref, eventId, component, mode, customVariables) {
2021-09-10 07:29:04 +00:00
const events = component.definition.events || [];
const filteredEvents = events.filter((event) => event.eventId === eventId);
for (const event of filteredEvents) {
await executeAction(_ref, event, mode, customVariables); // skipcq: JS-0032
}
}
export function onComponentClick(_ref, id, component, mode = 'edit') {
executeActionsForEventId(_ref, 'onClick', component, mode);
}
export function onQueryConfirm(_ref, queryConfirmationData) {
_ref.setState({
showQueryConfirmation: false,
});
runQuery(_ref, queryConfirmationData.queryId, queryConfirmationData.queryName, true);
}
export function onQueryCancel(_ref) {
_ref.setState({
showQueryConfirmation: false,
});
}
export async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
toast.success('Copied to clipboard!');
} catch (err) {
console.log('Failed to copy!', err);
}
}
function showModal(_ref, modal, show) {
const modalId = modal?.id ?? modal;
if (_.isEmpty(modalId)) {
console.log('No modal is associated with this event.');
return Promise.resolve();
}
const modalMeta = _ref.state.appDefinition.components[modalId];
const newState = {
currentState: {
..._ref.state.currentState,
components: {
..._ref.state.currentState.components,
[modalMeta.component.name]: {
..._ref.state.currentState.components[modalMeta.component.name],
show: show,
},
},
},
};
_ref.setState(newState);
return Promise.resolve();
}
function logoutAction(_ref) {
localStorage.clear();
_ref.props.history.push('/login');
window.location.href = '/login';
return Promise.resolve();
}
export const executeAction = (_ref, event, mode, customVariables) => {
console.log('nopski', customVariables);
if (event) {
switch (event.actionId) {
case 'show-alert': {
const message = resolveReferences(event.message, _ref.state.currentState, undefined, customVariables);
switch (event.alertType) {
case 'success':
case 'error':
toast[event.alertType](message);
break;
case 'info':
toast(message);
break;
case 'warning':
toast(message, {
icon: '⚠️',
});
break;
}
return Promise.resolve();
}
case 'run-query': {
const { queryId, queryName } = event;
return runQuery(_ref, queryId, queryName, true, mode);
}
case 'logout': {
return logoutAction(_ref);
}
2021-08-30 15:39:12 +00:00
case 'open-webpage': {
const url = resolveReferences(event.url, _ref.state.currentState, undefined, customVariables);
window.open(url, '_blank');
return Promise.resolve();
}
case 'go-to-app': {
const slug = resolveReferences(event.slug, _ref.state.currentState, undefined, customVariables);
const queryParams = event.queryParams?.reduce(
(result, queryParam) => ({
...result,
...{
[resolveReferences(queryParam[0], _ref.state.currentState)]: resolveReferences(
queryParam[1],
_ref.state.currentState,
undefined,
customVariables
),
},
}),
{}
);
let url = `/applications/${slug}`;
if (queryParams) {
const queryPart = serializeNestedObjectToQueryParams(queryParams);
if (queryPart.length > 0) url = url + `?${queryPart}`;
}
if (mode === 'view') {
_ref.props.history.push(url);
_ref.props.history.go();
} else {
if (confirm('The app will be opened in a new tab as the action is triggered from the editor.')) {
window.open(url, '_blank');
}
}
return Promise.resolve();
}
case 'show-modal':
return showModal(_ref, event.modal, true);
2021-05-09 18:06:11 +00:00
case 'close-modal':
return showModal(_ref, event.modal, false);
case 'copy-to-clipboard': {
const contentToCopy = resolveReferences(
event.contentToCopy,
_ref.state.currentState,
undefined,
customVariables
);
copyToClipboard(contentToCopy);
return Promise.resolve();
}
case 'set-localstorage-value': {
const key = resolveReferences(event.key, _ref.state.currentState, undefined, customVariables);
const value = resolveReferences(event.value, _ref.state.currentState, undefined, customVariables);
localStorage.setItem(key, value);
2021-11-24 09:33:28 +00:00
return Promise.resolve();
}
case 'generate-file': {
// const fileType = event.fileType;
const data = resolveReferences(event.data, _ref.state.currentState, undefined, customVariables) ?? [];
const fileName =
resolveReferences(event.fileName, _ref.state.currentState, undefined, customVariables) ?? 'data.txt';
const fileType =
resolveReferences(event.fileType, _ref.state.currentState, undefined, customVariables) ?? 'csv';
const fileData = {
csv: generateCSV,
plaintext: (plaintext) => plaintext,
}[fileType](data);
generateFile(fileName, fileData);
2021-11-24 09:33:28 +00:00
return Promise.resolve();
}
case 'set-table-page': {
setTablePageIndex(_ref, event.table, event.pageIndex);
break;
}
case 'set-custom-variable': {
const key = resolveReferences(event.key, _ref.state.currentState, undefined, customVariables);
const value = resolveReferences(event.value, _ref.state.currentState, undefined, customVariables);
const customAppVariables = { ..._ref.state.currentState.variables };
customAppVariables[key] = value;
return _ref.setState({
currentState: {
..._ref.state.currentState,
variables: customAppVariables,
},
});
}
case 'unset-custom-variable': {
const key = resolveReferences(event.key, _ref.state.currentState, undefined, customVariables);
const customAppVariables = { ..._ref.state.currentState.variables };
delete customAppVariables[key];
return _ref.setState({
currentState: {
..._ref.state.currentState,
variables: customAppVariables,
},
});
}
case 'control-component': {
const component = Object.values(_ref.state.currentState?.components ?? {}).filter(
(component) => component.id === event.componentId
)[0];
const action = component[event.componentSpecificActionHandle];
const actionArguments = _.map(event.componentSpecificActionParams, (param) => ({
...param,
value: resolveReferences(param.value, _ref.state.currentState, undefined, customVariables),
}));
const actionPromise = action(...actionArguments.map((argument) => argument.value));
return actionPromise ?? Promise.resolve();
}
}
}
};
export async function onEvent(_ref, eventName, options, mode = 'edit') {
let _self = _ref;
console.log('Event: ', eventName);
const { customVariables } = options;
if (eventName === 'onTrigger') {
const { component, queryId, queryName } = options;
_self.setState(
{
currentState: {
..._self.state.currentState,
components: {
..._self.state.currentState.components,
[component.name]: {
..._self.state.currentState.components[component.name],
},
},
},
},
() => {
runQuery(_ref, queryId, queryName, true, mode);
}
);
}
if (eventName === 'onRowClicked' && options?.component?.component === 'Table') {
const { component, data, rowId } = options;
_self.setState(
{
currentState: {
..._self.state.currentState,
components: {
..._self.state.currentState.components,
[component.name]: {
..._self.state.currentState.components[component.name],
selectedRow: data,
selectedRowId: rowId,
},
},
},
},
() => {
executeActionsForEventId(_ref, 'onRowClicked', component, mode, customVariables);
}
);
}
if (eventName === 'onRowClicked' && options?.component?.component === 'Listview') {
executeActionsForEventId(_ref, 'onRowClicked', options.component, mode, customVariables);
}
if (eventName === 'onCalendarEventSelect') {
const { component, calendarEvent } = options;
_self.setState(
{
currentState: {
..._self.state.currentState,
components: {
..._self.state.currentState.components,
[component.name]: {
..._self.state.currentState.components[component.name],
selectedEvent: { ...calendarEvent },
},
},
},
},
() => {
executeActionsForEventId(_ref, 'onCalendarEventSelect', component, mode, customVariables);
}
);
}
if (eventName === 'onCalendarSlotSelect') {
const { component, selectedSlots } = options;
_self.setState(
{
currentState: {
..._self.state.currentState,
components: {
..._self.state.currentState.components,
[component.name]: {
..._self.state.currentState.components[component.name],
selectedSlots,
},
},
},
},
() => {
executeActionsForEventId(_ref, 'onCalendarSlotSelect', component, mode, customVariables);
}
);
}
if (eventName === 'onTableActionButtonClicked') {
const { component, data, action, rowId } = options;
_self.setState(
{
currentState: {
..._self.state.currentState,
components: {
..._self.state.currentState.components,
[component.name]: {
..._self.state.currentState.components[component.name],
selectedRow: data,
selectedRowId: rowId,
},
},
},
},
async () => {
if (action && action.events) {
for (const event of action.events) {
if (event.actionId) {
// the event param uses a hacky workaround for using same format used by event manager ( multiple handlers )
await executeAction(_self, { ...event, ...event.options }, mode, customVariables);
}
}
} else {
console.log('No action is associated with this event');
}
}
);
}
if (eventName === 'OnTableToggleCellChanged') {
const { component, column, rowId, row } = options;
_self.setState(
{
currentState: {
..._self.state.currentState,
components: {
..._self.state.currentState.components,
[component.name]: {
..._self.state.currentState.components[component.name],
selectedRow: row,
selectedRowId: rowId,
},
},
},
},
async () => {
if (column && column.events) {
for (const event of column.events) {
if (event.actionId) {
// the event param uses a hacky workaround for using same format used by event manager ( multiple handlers )
await executeAction(_self, { ...event, ...event.options }, mode, customVariables);
}
}
} else {
console.log('No action is associated with this event');
}
}
);
}
if (
[
'onDetect',
'onCheck',
'onUnCheck',
'onBoundsChange',
'onCreateMarker',
'onMarkerClick',
'onPageChanged',
'onSearch',
'onChange',
'onEnterPressed',
'onSelectionChange',
'onSelect',
'onClick',
'onFileSelected',
'onFileLoaded',
'onFileDeselected',
'onStart',
'onResume',
'onReset',
'onPause',
'onCountDownFinish',
'onCalendarNavigate',
'onCalendarViewChange',
'onSearchTextChanged',
'onPageChange',
[Feature] Kanban board widget (#3049) * init kanban board widget * kanban board * reverts to beautifully * kanban UI updates and dnd fixes * bugfix: when dropped outside the col, should return back to it inital position * updates min-width of the column * container and widget styles * style fixes: column container onDrag * adds button for new group * fixes new card overflow * add btn for adding cards * groups and cards updated * add property definition * improves draggable card position while drag is active * handle delete group/col * handle col/group title updates * handles editing card title * style fixes for input cursor * cleanup * card popover with codehinter fields * minor card fixes * updates exposed variable * simplify boardData into cols and cards * adds width and min-width style definations * build board from queries * handle draggable rbd-id * removes add group card and delete group option * fixes typos * show empty state message * fixes typos * removes card extra border color * fixes column typi and cards updates issue * adds enableAddCard property defination * adds accent color options * default style accent color * accent color fix * revets popover with hinter * fixes card drag and drop * removes hook * fixes: state synced with property defination updates(col and cards data) * fixes: on re-arranging the card via dnd, update the card content * handles if card columnId is updated * adds card container layer * clean up * dark theme * fixes card onDrop issue * renamed the exposed variable data --> lists * adds custom resolvers to the popover * handle widget crash when non iterables are passed * updates default card and col value * fixes dnd issues for dynamic card values * refactor: cleanup * handles empty and undefined cardData * fixes Height of widget is changing when popover thing is displayed. * fixes: updating card data in widget inspector * fixes: updating column data in widget inspector * fixes adding cards for newly created groups/columns * clean up * Add kanban event onCardAdded and expose lastAddedCard * Add onCardRemoved action and expsed lastRemovedCard variable * Add events and variables for card movement and selection * Add card edit feature for kanban widget * Rename lastAddedRemoved to lastRemovedCard in kanban * Rename lists to columns on kanban board * Set max height of kanban column to respond to widget height * Have "Add description" link if there is no description for Kanban cards * kanban docs * Change text from "add +" to "Add card" on kanban * Validate card data before update * Add tip about card id type on kanban documentation * Add default min width and width for kanban Co-authored-by: Sherfin Shamsudeen <[email protected]> Co-authored-by: Shubhendra <[email protected]>
2022-06-14 05:36:36 +00:00
'onCardAdded',
'onCardRemoved',
'onCardMoved',
'onCardSelected',
'onCardUpdated',
'onTabSwitch',
].includes(eventName)
) {
const { component } = options;
executeActionsForEventId(_ref, eventName, component, mode, customVariables);
}
if (eventName === 'onBulkUpdate') {
onComponentOptionChanged(_self, options.component, 'isSavingChanges', true);
await executeActionsForEventId(_self, eventName, options.component, mode, customVariables);
onComponentOptionChanged(_self, options.component, 'isSavingChanges', false);
}
if (['onDataQuerySuccess', 'onDataQueryFailure'].includes(eventName)) {
await executeActionsForEventId(_self, eventName, options, mode, customVariables);
}
}
2022-02-23 04:18:05 +00:00
export function getQueryVariables(options, state) {
let queryVariables = {};
const optionsType = typeof options;
switch (optionsType) {
case 'string': {
options = options.replace(/\n/g, ' ');
const dynamicVariables = getDynamicVariables(options) || [];
dynamicVariables.forEach((variable) => {
queryVariables[variable] = resolveReferences(variable, state);
});
break;
}
case 'object': {
if (Array.isArray(options)) {
options.forEach((element) => {
_.merge(queryVariables, getQueryVariables(element, state));
});
} else {
Object.keys(options || {}).forEach((key) => {
_.merge(queryVariables, getQueryVariables(options[key], state));
});
}
break;
}
default:
break;
}
return queryVariables;
}
export function previewQuery(_ref, query, editorState, calledFromQuery = false) {
const options = getQueryVariables(query.options, _ref.props.currentState);
2021-05-22 11:29:27 +00:00
_ref.setState({ previewLoading: true });
return new Promise(function (resolve, reject) {
let queryExecutionPromise = null;
if (query.kind === 'runjs') {
queryExecutionPromise = executeMultilineJS(_ref, query.options.code, editorState, true);
} else {
queryExecutionPromise = dataqueryService.preview(query, options);
}
queryExecutionPromise
.then((data) => {
let finalData = data.data;
if (query.options.enableTransformation) {
finalData = runTransformation(_ref, finalData, query.options.transformation, query);
}
if (calledFromQuery) {
_ref.setState({ previewLoading: false });
} else {
_ref.setState({ previewLoading: false, queryPreviewData: finalData });
}
switch (data.status) {
case 'failed': {
toast.error(`${data.message}: ${data.description}`);
break;
}
case 'needs_oauth': {
const url = data.data.auth_url; // Backend generates and return sthe auth url
fetchOAuthToken(url, query.data_source_id);
break;
}
case 'ok': {
toast(`Query completed.`, {
icon: '🚀',
});
break;
}
2021-07-25 17:32:36 +00:00
}
resolve({ status: data.status, data: finalData });
})
.catch(({ error, data }) => {
_ref.setState({ previewLoading: false, queryPreviewData: data });
toast.error(error);
reject({ error, data });
});
2021-05-22 11:29:27 +00:00
});
}
export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode) {
const query = _ref.state.app.data_queries.find((query) => query.id === queryId);
let dataQuery = {};
if (query) {
dataQuery = JSON.parse(JSON.stringify(query));
} else {
toast.error('No query has been associated with the action.');
return;
}
const options = getQueryVariables(dataQuery.options, _ref.state.currentState);
if (dataQuery.options.requestConfirmation) {
if (confirmed === undefined) {
_ref.setState({
showQueryConfirmation: true,
queryConfirmationData: {
queryId,
queryName,
},
});
return;
}
}
const newState = {
..._ref.state.currentState,
queries: {
..._ref.state.currentState.queries,
[queryName]: {
..._ref.state.currentState.queries[queryName],
isLoading: true,
data: [],
rawData: [],
},
},
errors: {},
};
let _self = _ref;
2021-12-30 11:57:02 +00:00
// eslint-disable-next-line no-unused-vars
return new Promise(function (resolve, reject) {
_self.setState({ currentState: newState }, () => {
let queryExecutionPromise = null;
if (query.kind === 'runjs') {
queryExecutionPromise = executeMultilineJS(_self, query.options.code, _ref, false, confirmed, mode);
} else {
queryExecutionPromise = dataqueryService.run(queryId, options);
}
queryExecutionPromise
.then((data) => {
if (data.status === 'needs_oauth') {
const url = data.data.auth_url; // Backend generates and return sthe auth url
fetchOAuthToken(url, dataQuery.data_source_id);
}
if (data.status === 'failed') {
console.error(data.message);
return _self.setState(
{
currentState: {
..._self.state.currentState,
queries: {
..._self.state.currentState.queries,
[queryName]: _.assign(
{
..._self.state.currentState.queries[queryName],
isLoading: false,
},
query.kind === 'restapi'
? {
request: data.data.requestObject,
response: data.data.responseObject,
responseHeaders: data.data.responseHeaders,
}
: {}
),
},
errors: {
..._self.state.currentState.errors,
[queryName]: {
type: 'query',
kind: query.kind,
data: data,
options: options,
},
},
},
},
() => {
resolve(data);
onEvent(_self, 'onDataQueryFailure', { definition: { events: dataQuery.options.events } });
}
);
}
let rawData = data.data;
let finalData = data.data;
if (dataQuery.options.enableTransformation) {
finalData = runTransformation(_self, rawData, dataQuery.options.transformation, dataQuery);
if (finalData.status === 'failed') {
return _self.setState(
{
currentState: {
..._self.state.currentState,
queries: {
..._self.state.currentState.queries,
[queryName]: {
..._self.state.currentState.queries[queryName],
isLoading: false,
},
},
errors: {
..._self.state.currentState.errors,
[queryName]: {
type: 'transformations',
data: finalData,
options: options,
},
},
},
},
() => {
resolve(finalData);
onEvent(_self, 'onDataQueryFailure', { definition: { events: dataQuery.options.events } });
}
);
}
}
if (dataQuery.options.showSuccessNotification) {
const notificationDuration = dataQuery.options.notificationDuration * 1000 || 5000;
toast.success(dataQuery.options.successMessage, {
duration: notificationDuration,
});
}
if (dataQuery.options.requestConfirmation) {
toast(`Query (${dataQuery.name}) completed.`);
}
_self.setState(
{
currentState: {
..._self.state.currentState,
queries: {
..._self.state.currentState.queries,
[queryName]: _.assign(
{
..._self.state.currentState.queries[queryName],
isLoading: false,
data: finalData,
rawData,
},
query.kind === 'restapi'
? { request: data.request, response: data.response, responseHeaders: data.responseHeaders }
: {}
),
},
},
},
() => {
resolve({ status: 'ok', data: finalData });
onEvent(_self, 'onDataQuerySuccess', { definition: { events: dataQuery.options.events } }, mode);
}
);
})
.catch(({ error }) => {
toast.error(error);
_self.setState(
{
currentState: {
..._self.state.currentState,
queries: {
..._self.state.currentState.queries,
[queryName]: {
isLoading: false,
},
},
},
},
() => {
resolve({ status: 'failed', message: error });
}
);
});
});
});
}
2021-05-28 12:31:13 +00:00
2022-02-23 04:18:05 +00:00
export function setTablePageIndex(_ref, tableId, index) {
if (_.isEmpty(tableId)) {
console.log('No table is associated with this event.');
return Promise.resolve();
}
const table = Object.entries(_ref.state.currentState.components).filter((entry) => entry[1].id === tableId)[0][1];
const newPageIndex = resolveReferences(index, _ref.state.currentState);
table.setPage(newPageIndex ?? 1);
return Promise.resolve();
}
export function renderTooltip({ props, text }) {
2022-07-27 04:26:09 +00:00
if (text === '') return <></>;
return (
<Tooltip id="button-tooltip" {...props}>
{text}
</Tooltip>
);
}
2021-09-13 07:17:45 +00:00
2022-02-23 04:18:05 +00:00
export function computeComponentState(_ref, components = {}) {
2021-09-13 07:17:45 +00:00
let componentState = {};
const currentComponents = _ref.state.currentState.components;
Object.keys(components).forEach((key) => {
const component = components[key];
const componentMeta = componentTypes.find((comp) => component.component.component === comp.component);
const existingComponentName = Object.keys(currentComponents).find((comp) => currentComponents[comp].id === key);
const existingValues = currentComponents[existingComponentName];
if (component.parent) {
const parentComponent = components[component.parent];
let isListView = false;
try {
isListView = parentComponent.component.component === 'Listview';
} catch {
console.log('error');
}
if (!isListView) {
componentState[component.component.name] = { ...componentMeta.exposedVariables, id: key, ...existingValues };
}
} else {
componentState[component.component.name] = { ...componentMeta.exposedVariables, id: key, ...existingValues };
}
2021-09-13 07:17:45 +00:00
});
return setStateAsync(_ref, {
currentState: {
..._ref.state.currentState,
components: {
...componentState,
2021-09-13 07:17:45 +00:00
},
},
defaultComponentStateComputed: true,
});
}
export const getSvgIcon = (key, height = 50, width = 50) => {
const Icon = allSvgs[key];
return <Icon style={{ height, width }} />;
};
Feature/component property validation (#2782) * Initial architecture for component property validation * Coerce to default and log invalid properties * eslint rule:frontend for specifying the path to the @types/ * removes comment for eslint-disable-next-line * reverts 27946f1 & adding a temporary fix * Remove incorrect property validations * Avoid race condiiton in setting state for error logs * Fix issue where only one error got logged * Flush out any errors that are logged * Remove unnecessary console.log * Add validations for Table properties * Add support for multiple validations * Add validation for chart component * Add validation for modal * Set default value common to all validation schemas for component properties * Add validations for password widget * Add validations for datepicker * Add information about default value on validation error message * Remove unwanted console.log * Do not validate properties on Viewer * Use meta information from widget config instead of component state to validate * Do not coerce to default values while validating * Do not validate existing components * Update package-lock.json in sync with develop * Add validation for general properties * Add support for size validation of component properties and styles * Add support for min and max spec in size validation * Support pattern validation for string properties * Make size validation specifiable along with type specification * Component validation optimizations * Remove unnecessary comments * Remove unnecessary default value param from validation * Fixed visibility style validation bug * Added custom validation to PDF & Custom component * property/style validation statistics * values changed to string * validation button group widget * bugfix * Added property validation to timeline widget * Added visibility prop validation to timeline widget * steps property/style validation * svg component property validation * component property validation numberinput * bugfix * validation textarea * validation vertical divider * property validation html widget * validation :: checkbox * image property validation * validation :: rangeslider widget * validation :: circular progress bar * validation spinner * added props and style validation * tags validation * validation :: pagination * timer :: validation * validation :: toggle * validation :: divider * validation :: radiobutton * added props and style validation * validation:: iframe * validation :: password input * validation:: code editor * validation :: listview * validation :: star rating * validation :: modal * validation :: qrscanner * validation :: datepicker * multiselect :: validation * added union validation for border radius * added props and style validation * added props and styles validation * added props and styles validations * added props and styles validations * added props and style validations * added props and styles validations * added prop and style validations * added props and styles validations * Added ID validation to steps widget * Removed default value * Added validation key to SVG widget * Removed default value * table validations * table validations * removing default value * removing defaultval * removing default val * removing default val * padding validation update * updating number validation * updatin number validation * validation updates * border validation * border radius validation * number input validation * validation updates * border radius validation * validation update * Updated misspelled schema text * Updated Tabs validation schema * Updated tooltip element schema * Updated validation for multi select * Updated validation for dropdown * Updated validation for text widget * Rectified mispelled validation * Fixed : validation not working for format * Added Array validation to chart widget * format prop bug fix * draft complete :: table validation * Fixed misspelled text * Don't validate properties that are not defined on widgetConfig Co-authored-by: arpitnath <[email protected]> Co-authored-by: Kavin Venkatachalam <[email protected]> Co-authored-by: stepinfwd <[email protected]> Co-authored-by: manishkushare <[email protected]> Co-authored-by: Kavin Venkatachalam <50441969+[email protected]>
2022-07-19 13:21:45 +00:00
export const debuggerActions = {
error: (_self, errors) => {
_self.setState((prevState) => ({
...prevState,
currentState: {
...prevState.currentState,
errors: {
...prevState.currentState.errors,
...errors,
},
},
}));
},
flush: (_self) => {
_self.setState((prevState) => ({
...prevState,
currentState: {
...prevState.currentState,
errors: {},
},
}));
},
};
export const getComponentName = (currentState, id) => {
try {
const name = Object.entries(currentState?.components).filter(([_, component]) => component.id === id)[0][0];
return name;
} catch {
return '';
}
};
const updateNewComponents = (appDefinition, newComponents, updateAppDefinition) => {
const newAppDefinition = JSON.parse(JSON.stringify(appDefinition));
newComponents.forEach((newComponent) => {
newComponent.component.name = computeComponentName(newComponent.component.component, newAppDefinition.components);
newAppDefinition.components[newComponent.id] = newComponent;
});
updateAppDefinition(newAppDefinition);
};
export const cloneComponents = (_ref, updateAppDefinition, isCloning = true) => {
const { selectedComponents, appDefinition } = _ref.state;
const { components: allComponents } = appDefinition;
let newComponents = [];
for (let selectedComponent of selectedComponents) {
const component = {
id: selectedComponent.id,
component: allComponents[selectedComponent.id]?.component,
layouts: allComponents[selectedComponent.id]?.layouts,
parent: allComponents[selectedComponent.id]?.parent,
};
let clonedComponent = JSON.parse(JSON.stringify(component));
clonedComponent.parent = undefined;
clonedComponent.children = [];
clonedComponent.children = [...getChildComponents(allComponents, component, clonedComponent)];
newComponents = [...newComponents, clonedComponent];
}
if (isCloning) {
addComponents(appDefinition, updateAppDefinition, undefined, newComponents, true);
toast.success('Component cloned succesfully');
} else {
navigator.clipboard.writeText(JSON.stringify(newComponents));
toast.success('Component copied succesfully');
}
_ref.setState({ currentSidebarTab: 2 });
};
const getChildComponents = (allComponents, component, parentComponent) => {
let childComponents = [],
selectedChildComponents = [];
if (component.component.component === 'Tabs' || component.component.component === 'Calendar') {
childComponents = Object.keys(allComponents).filter((key) => allComponents[key].parent?.startsWith(component.id));
} else {
childComponents = Object.keys(allComponents).filter((key) => allComponents[key].parent === component.id);
}
childComponents.forEach((componentId) => {
let childComponent = JSON.parse(JSON.stringify(allComponents[componentId]));
childComponent.id = componentId;
const newComponent = JSON.parse(
JSON.stringify({
id: componentId,
component: allComponents[componentId]?.component,
layouts: allComponents[componentId]?.layouts,
parent: allComponents[componentId]?.parent,
})
);
if ((component.component.component === 'Tabs') | (component.component.component === 'Calendar')) {
const childTabId = childComponent.parent.split('-').at(-1);
childComponent.parent = `${parentComponent.id}-${childTabId}`;
} else {
childComponent.parent = parentComponent.id;
}
parentComponent.children = [...(parentComponent.children || []), childComponent];
childComponent.children = [...getChildComponents(allComponents, newComponent, childComponent)];
selectedChildComponents.push(childComponent);
});
return selectedChildComponents;
};
const updateComponentLayout = (components, parentId) => {
let prevComponent;
components.forEach((component, index) => {
Object.keys(component.layouts).map((layout) => {
if (parentId !== undefined) {
if (index > 0) {
component.layouts[layout].top = prevComponent.layouts[layout].top + prevComponent.layouts[layout].height;
component.layouts[layout].left = 0;
} else {
component.layouts[layout].top = 0;
component.layouts[layout].left = 0;
}
prevComponent = component;
} else {
component.layouts[layout].top = component.layouts[layout].top + component.layouts[layout].height;
}
});
});
};
export const addComponents = (
appDefinition,
appDefinitionChanged,
parentId = undefined,
pastedComponent = [],
isCloning = false
) => {
const finalComponents = [];
let parentComponent = undefined;
if (parentId) {
const id = Object.keys(appDefinition.components).filter((key) => parentId.startsWith(key));
parentComponent = JSON.parse(JSON.stringify(appDefinition.components[id[0]]));
parentComponent.id = parentId;
}
!isCloning && updateComponentLayout(pastedComponent, parentId);
const buildComponents = (components, parentComponent = undefined, skipTabCalendarCheck = false) => {
if (Array.isArray(components) && components.length > 0) {
components.forEach((component) => {
const newComponent = {
id: uuidv4(),
component: component?.component,
layouts: component?.layouts,
};
if (parentComponent) {
if (
!skipTabCalendarCheck &&
(parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar')
) {
const childTabId = component.parent.split('-').at(-1);
newComponent.parent = `${parentComponent.id}-${childTabId}`;
} else {
newComponent.parent = parentComponent.id;
}
}
finalComponents.push(newComponent);
if (component.children.length > 0) {
buildComponents(component.children, newComponent);
}
});
}
};
buildComponents(pastedComponent, parentComponent, true);
updateNewComponents(appDefinition, finalComponents, appDefinitionChanged);
!isCloning && toast.success('Component pasted succesfully');
};