UNPKG

@adaptabletools/adaptable-cjs

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

254 lines (253 loc) 16.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.baseClassName = void 0; exports.ExpressionEditor = ExpressionEditor; const tslib_1 = require("tslib"); const React = tslib_1.__importStar(require("react")); const react_1 = require("react"); const DocumentationLinkConstants_1 = require("../../Utilities/Constants/DocumentationLinkConstants"); const ModuleConstants_1 = require("../../Utilities/Constants/ModuleConstants"); const ArrayExtensions_1 = tslib_1.__importDefault(require("../../Utilities/Extensions/ArrayExtensions")); const StringExtensions_1 = tslib_1.__importDefault(require("../../Utilities/Extensions/StringExtensions")); const AdaptableInput_1 = tslib_1.__importDefault(require("../../View/Components/AdaptableInput")); const ButtonInfo_1 = require("../../View/Components/Buttons/ButtonInfo"); const CheckBox_1 = require("../CheckBox"); const CodeBlock_1 = require("../CodeBlock"); const HelpBlock_1 = tslib_1.__importDefault(require("../HelpBlock")); const Tabs_1 = require("../Tabs"); const DataTableEditor_1 = require("./DataTableEditor"); const EditorContext_1 = require("./EditorContext"); const EditorInput_1 = tslib_1.__importDefault(require("./EditorInput")); const EditorInputWithWhereClause_1 = tslib_1.__importDefault(require("./EditorInputWithWhereClause")); const ExpressionFunctionDocumentation_1 = require("./ExpressionFunctionDocumentation"); const NamedQueryContext_1 = require("./NamedQueryContext"); const NamedQueryEditor_1 = require("./NamedQueryEditor"); const QueryBuilder_1 = require("./QueryBuilder"); const Flex_1 = require("../Flex"); const twMerge_1 = require("../../twMerge"); exports.baseClassName = `ab-ExpressionEditor`; function ExpressionEditor(props) { const { type, module } = props; const [selectedFunction, setSelectedFunction] = (0, react_1.useState)(null); const [expressionText, setExpressionText] = (0, react_1.useState)(props.value); const { namedQuery, setNamedQuery } = (0, NamedQueryContext_1.useNamedQueryContext)(); const textAreaRef = (0, react_1.useRef)(null); const allowSaveNamedQuery = props.allowSaveNamedQuery ?? type === 'boolean'; const editorInput = type === 'observable' || type === 'aggregatedBoolean' ? (React.createElement(EditorInputWithWhereClause_1.default, { type: type, module: module, value: props.value, onChange: (value) => { setExpressionText(value); props.onChange(value); }, testData: props.initialData, api: props.api })) : ( // 'boolean','scalar','aggregatedScalar'/'cumulativeAggregatedScalar'/'quantileAggregatedScalar' (React.createElement( EditorInput_1.default, { type: type, module: module, value: props.value, onChange: (value) => { setExpressionText(value); props.onChange(value); }, testData: props.initialData, isFullExpression: props.isFullExpression, api: props.api } ))); const adaptableFields = props.api.expressionApi.internalApi.getAvailableFields(); // currently only boolean and scalar expressions support nested calculated columns (calc cols which reference other calc cols) const queryableColumns = type === 'scalar' || type === 'boolean' || type === 'aggregatedScalar' ? props.columns : props.columns.filter((c) => !c.isCalculatedColumn); const showDocumentationLinks = props.api.internalApi.isDocumentationLinksDisplayed(); // @Bogdan i did this to be sure but i think we can get rid of this as the button does the same (and better IMO) const showNamedQueryStuff = false; const queryDocumentationLink = queryDocumentationLinks[type]; const saveAsNamedQueryElement = allowSaveNamedQuery && props.api.internalApi .getModuleService() .getModuleById(ModuleConstants_1.NamedQueryModuleId) .isModuleEditable() && (React.createElement(Flex_1.Flex, { flexDirection: "row", className: "twa:mb-2 twa:text-3" }, React.createElement(CheckBox_1.CheckBox, { checked: !!namedQuery, onChange: (checked) => { if (checked) { setNamedQuery({ Name: '', BooleanExpression: expressionText, }); } else { setNamedQuery(false); } }, className: "twa:mr-2 twa:items-center" }, "Save as new Named Query"), namedQuery ? (React.createElement(AdaptableInput_1.default, { className: "twa:flex-1", onChange: (event) => { const { target: { value }, } = event; setNamedQuery({ Name: value, BooleanExpression: expressionText, }); } })) : null)); const editorElement = (React.createElement(Flex_1.Flex, { className: (0, twMerge_1.twMerge)(`${exports.baseClassName} twa:flex-1 twa:pl-2 twa:overflow-auto`, props.className), "data-name": "expression-editor-wrapper", style: props.style }, React.createElement(Flex_1.Flex, { className: "twa:flex-1", style: { minHeight: 0 }, flexDirection: "column", onFocus: (event) => { if (event.target.tagName === 'TEXTAREA') { textAreaRef.current = event.target; } } }, React.createElement(Flex_1.Flex, { className: "twa:flex-1 twa:text-2", flexDirection: "row", "data-name": "expression-editor", style: { minHeight: 0 } }, React.createElement(Flex_1.Flex, { className: "twa:flex-1 twa:pr-2 twa:overflow-auto twa:min-h-0", "data-name": "expression-builder", flexDirection: "column" }, editorInput, StringExtensions_1.default.IsNotNullOrEmpty(expressionText?.trim()) && (React.createElement(ExpressionFunctionDocumentation_1.ExpressionFunctionDocumentation, { expressionFunction: selectedFunction })), /* displayed for advanced queries (observable&Aggregation) to give the users a starting point */ StringExtensions_1.default.IsNullOrEmpty(expressionText?.trim()) && renderQueryHints(type), showDocumentationLinks && (React.createElement(HelpBlock_1.default, { "data-name": "query-documentation", className: "twa:my-2 twa:p-2 twa:text-3" }, React.createElement(ButtonInfo_1.ButtonInfo, { className: "twa:mr-2", onClick: () => window.open(queryDocumentationLink, '_blank') }), "See documentation for more details and examples")), React.createElement(Flex_1.Box, { className: "twa:flex-1" }), showNamedQueryStuff && saveAsNamedQueryElement), React.createElement(Flex_1.Box, { className: `${exports.baseClassName}__sidebar twa:pb-2 twa:px-2 twa:overflow-auto twa:h-full twa:w-[280px]`, "data-name": "expression-sidebar" }, React.createElement(Flex_1.Box, { className: "twa:h-full twa:p-2 twa:rounded-standard twa:shadow-md twa:bg-defaultbackground twa:text-text-on-defaultbackground" }, React.createElement(Tabs_1.Tabs, { className: "twa:h-full" }, React.createElement(Tabs_1.Tabs.Tab, { value: "column" }, "Columns"), React.createElement(Tabs_1.Tabs.Tab, { value: "field" }, "Fields"), React.createElement(Tabs_1.Tabs.Tab, { value: "named-query" }, "Named Queries"), React.createElement(Tabs_1.Tabs.Content, { className: "twa:flex-1 twa:h-full" }, React.createElement(DataTableEditor_1.DataTableEditor, { type: "column", dataFormatter: (value) => `[${value}]`, fields: queryableColumns.map((column) => ({ label: column.friendlyName, value: column.columnId, dataType: column.dataType, readOnly: column.readOnly, })), data: props.initialData })), React.createElement(Tabs_1.Tabs.Content, null, adaptableFields && adaptableFields.length > 0 ? (React.createElement(DataTableEditor_1.DataTableEditor, { type: "field", labels: { showIds: 'Show Field path', filterPlaceholder: 'Filter fields...', }, dataFormatter: (value) => `FIELD("${value}")`, data: props.initialData, fields: adaptableFields?.map((field) => ({ label: field.label, value: field.name, dataType: field.dataType, readOnly: true, })) })) : (React.createElement(Flex_1.Box, { className: "twa:p-2" }, "No Data Fields provided"))), React.createElement(Tabs_1.Tabs.Content, null, ArrayExtensions_1.default.IsNullOrEmpty(props.namedQueries) ? (React.createElement("div", null, React.createElement(Flex_1.Box, { className: "twa:p-2" }, "No Named Queries defined"), React.createElement(Flex_1.Box, { className: "twa:text-2 twa:p-2" }, "Named Queries are saved Expressions which can be referenced in other Expressions using the ", React.createElement("i", null, "QUERY"), " keyword", ' '))) : (React.createElement(NamedQueryEditor_1.NamedQueryEditor, { namedQueries: props.namedQueries })))))))))); const queryBuilderElement = (React.createElement(QueryBuilder_1.QueryBuilder, { module: module, query: expressionText, onChange: (query) => { setExpressionText(query); props.onChange(query); }, getFields: (type) => { return props.fields .filter((field) => { if (!type) { return (field.dataType === 'text' || field.dataType === 'number' || field.dataType === 'boolean' || field.dataType === 'date'); } else { return field.dataType === type; } }) .map((field) => ({ value: field.name, label: field.label, type: field.dataType, })); }, getColumns: (type) => { return props.columns .filter((column) => { if (!type) { return (column.dataType === 'text' || column.dataType === 'number' || column.dataType === 'boolean' || column.dataType === 'date'); } else { return column.dataType === type; } }) .map((col) => ({ value: col.columnId, label: col.friendlyName ?? col.columnId, type: col.dataType, })); } })); const showQueryBuilder = props.showQueryBuilder ?? false; const showExpressionEditor = props.showExpressionEditor ?? true; return (React.createElement(EditorContext_1.ExpressionEditorContext.Provider, { value: { selectedFunction, setSelectedFunction, textAreaRef, } }, (() => { switch (true) { case showQueryBuilder && showExpressionEditor: return (React.createElement(Tabs_1.Tabs, { "data-name": "editor-selector-tabs", className: "twa:mb-2 twa:p-2 twa:min-h-0 twa:flex-1" }, React.createElement(Tabs_1.Tabs.Tab, { value: "editor" }, "Expression Editor"), React.createElement(Tabs_1.Tabs.Tab, { value: "ui" }, "Query Builder"), React.createElement(Tabs_1.Tabs.Content, null, editorElement), React.createElement(Tabs_1.Tabs.Content, null, React.createElement(Flex_1.Flex, { flexDirection: "column", className: "twa:h-full" }, queryBuilderElement, React.createElement(Flex_1.Box, { className: "twa:flex-1" }), showNamedQueryStuff && saveAsNamedQueryElement)))); case showQueryBuilder: return (React.createElement(Flex_1.Flex, { flexDirection: "column", className: "twa:p-2 twa:h-full" }, queryBuilderElement)); case showExpressionEditor: return (React.createElement(Flex_1.Flex, { flexDirection: "column", className: "twa:p-2 twa:h-full" }, editorElement)); default: return null; } })())); } const renderQueryHints = (type) => { const examples = []; if (type === 'observable') { examples.push({ code: "ROW_CHANGE( COUNT([ItemCount],3), TIMEFRAME('5m'))", description: 'The Item Count value in a specific Row changes 3 times within a 5 minute timeframe', }); examples.push({ code: "GRID_CHANGE( MAX([OrderCost]), TIMEFRAME('1h')) WHERE [CustomerReference] = 'TRADH'", description: "An Order Cost cell contains its highest value in the whole Grid within the last hour - for rows where Cust Ref is 'TRADH'", }); } if (type === 'aggregatedBoolean') { examples.push({ code: 'SUM([PnL]) > 50000000', description: "The sum of the 'PnL' column values in all rows is greater than 5 Million", }); examples.push({ code: "SUM([PnL]) > '5B' WHERE QUERY('CurrencyDollar')", description: "The sum of the 'PnL' column values in all rows where named query 'CurrencyDollar' is evaluated to TRUE is greater than 5 Billions", }); } if (type === 'aggregatedScalar') { examples.push({ code: 'AVG([stargazers_count], GROUP_BY([language]))', description: 'Average popularity (number of stars) of all the Github repositories, grouped by programming language', }, { code: 'MAX([PnL]), GROUP_BY([currency], [country])', description: "The maximum 'PnL' value, grouped by currency and country", }); } if (type === 'cumulativeAggregatedScalar') { examples.push({ code: 'CUMUL(AVG([stargazers_count], GROUP_BY([language])), OVER([TradeDate]))', description: "The cumulative average popularity (number of stars) of all the Github repositories, grouped by programming language in the order given by the 'TradeDate' column", }, { code: 'CUMUL( SUM([PnL]), OVER([TradeDate]))', description: "The cumulative sum of all 'PnL' columns in the order given by the 'TradeDate' column", }); } return examples.length ? (React.createElement(Flex_1.Box, { "data-name": "expression-hints", className: "twa:my-2 twa:p-2 twa:bg-primary twa:rounded-standard" }, React.createElement(Flex_1.Box, null, React.createElement(Flex_1.Box, { className: "twa:mb-2" }, React.createElement("b", null, "Examples"), " (click on each to see its explanation):"), examples.map((example, index) => (React.createElement("details", { key: index, className: "twa:mb-2" }, React.createElement(Flex_1.Flex, { className: "twa:mr-2 twa:rounded-standard twa:mb-1 twa:text-2", alignItems: "center", as: "summary" }, React.createElement(CodeBlock_1.CodeBlock, null, " ", example.code)), React.createElement(Flex_1.Box, { className: "twa:ml-3 twa:italic twa:text-2" }, example.description))))))) : null; }; const queryDocumentationLinks = { boolean: DocumentationLinkConstants_1.BooleanQueryDocsLink, scalar: DocumentationLinkConstants_1.ScalarQueryDocsLink, observable: DocumentationLinkConstants_1.ObservableQueryDocsLink, aggregatedBoolean: DocumentationLinkConstants_1.AggregatedBooleanQueryDocsLink, aggregatedScalar: DocumentationLinkConstants_1.AggregatedScalarQueryDocsLink, cumulativeAggregatedScalar: DocumentationLinkConstants_1.CumulativeAggregatedScalarQueryDocsLink, quantileAggregatedScalar: DocumentationLinkConstants_1.QuantileAggregatedScalarQueryDocsLink, };