mirror of
https://github.com/ToolJet/ToolJet
synced 2026-04-21 13:37:28 +00:00
Merge branch 'lts-3.16' into rebase/lts-main-15-mgs
This commit is contained in:
commit
102c4e7cab
18 changed files with 307 additions and 61 deletions
|
|
@ -19,12 +19,12 @@ import useSidebarMargin from './Hooks/useSidebarMargin';
|
|||
import useAppPageSidebarHeight from './Hooks/useAppPageSidebarHeight';
|
||||
import { Container } from './Container';
|
||||
import { SuspenseCountProvider } from './SuspenseTracker';
|
||||
import { MobileLayout } from './MobileLayout';
|
||||
import { DesktopLayout } from './DesktopLayout';
|
||||
// Lazy load editor-only component to reduce viewer bundle size
|
||||
const AppCanvasBanner = lazy(() => import('@/AppBuilder/Header/AppCanvasBanner'));
|
||||
const EditorSelecto = React.lazy(() => import('./Selecto'));
|
||||
const Grid = React.lazy(() => import('./Grid'));
|
||||
const MobileLayout = lazy(() => import('./MobileLayout').then((m) => ({ default: m.MobileLayout })));
|
||||
const DesktopLayout = lazy(() => import('./DesktopLayout').then((m) => ({ default: m.DesktopLayout })));
|
||||
import useCanvasMinWidth from './Hooks/useCanvasMinWidth';
|
||||
import useEnableMainCanvasScroll from './Hooks/useEnableMainCanvasScroll';
|
||||
import useCanvasResizing from './Hooks/useCanvasResizing';
|
||||
|
|
@ -264,50 +264,48 @@ export const AppCanvas = ({ appId, switchDarkMode, darkMode }) => {
|
|||
onAllResolved={handleAllSuspenseResolved}
|
||||
deferCheck={isModuleMode || appType === 'module'}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
{isMobileLayout ? (
|
||||
<MobileLayout
|
||||
pageKey={pageKey}
|
||||
showCanvasHeader={showCanvasHeader}
|
||||
showCanvasFooter={showCanvasFooter}
|
||||
isMobileLayout={isMobileLayout}
|
||||
currentMode={currentMode}
|
||||
appType={appType}
|
||||
currentPageId={currentPageId}
|
||||
homePageId={homePageId}
|
||||
switchDarkMode={switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
canvasMaxWidth={canvasMaxWidth}
|
||||
isAppDarkMode={isAppDarkMode}
|
||||
mainCanvasContainer={mainCanvasContainer}
|
||||
canvasHeaderHeight={canvasHeaderHeight}
|
||||
/>
|
||||
) : (
|
||||
<DesktopLayout
|
||||
pageKey={pageKey}
|
||||
isModuleMode={isModuleMode}
|
||||
isMobileLayout={isMobileLayout}
|
||||
showCanvasHeader={showCanvasHeader}
|
||||
showCanvasFooter={showCanvasFooter}
|
||||
position={position}
|
||||
isPagesSidebarHidden={isPagesSidebarHidden}
|
||||
appType={appType}
|
||||
sideBarVisibleHeight={sideBarVisibleHeight}
|
||||
currentPageId={currentPageId}
|
||||
homePageId={homePageId}
|
||||
switchDarkMode={switchDarkMode}
|
||||
isViewerSidebarPinned={isViewerSidebarPinned}
|
||||
setIsSidebarPinned={setIsSidebarPinned}
|
||||
darkMode={darkMode}
|
||||
canvasMaxWidth={canvasMaxWidth}
|
||||
canvasContentRef={canvasContentRef}
|
||||
currentMode={currentMode}
|
||||
isAppDarkMode={isAppDarkMode}
|
||||
mainCanvasContainer={mainCanvasContainer}
|
||||
canvasHeaderHeight={canvasHeaderHeight}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
{isMobileLayout ? (
|
||||
<MobileLayout
|
||||
pageKey={pageKey}
|
||||
showCanvasHeader={showCanvasHeader}
|
||||
showCanvasFooter={showCanvasFooter}
|
||||
isMobileLayout={isMobileLayout}
|
||||
currentMode={currentMode}
|
||||
appType={appType}
|
||||
currentPageId={currentPageId}
|
||||
homePageId={homePageId}
|
||||
switchDarkMode={switchDarkMode}
|
||||
darkMode={darkMode}
|
||||
canvasMaxWidth={canvasMaxWidth}
|
||||
isAppDarkMode={isAppDarkMode}
|
||||
mainCanvasContainer={mainCanvasContainer}
|
||||
canvasHeaderHeight={canvasHeaderHeight}
|
||||
/>
|
||||
) : (
|
||||
<DesktopLayout
|
||||
pageKey={pageKey}
|
||||
isModuleMode={isModuleMode}
|
||||
isMobileLayout={isMobileLayout}
|
||||
showCanvasHeader={showCanvasHeader}
|
||||
showCanvasFooter={showCanvasFooter}
|
||||
position={position}
|
||||
isPagesSidebarHidden={isPagesSidebarHidden}
|
||||
appType={appType}
|
||||
sideBarVisibleHeight={sideBarVisibleHeight}
|
||||
currentPageId={currentPageId}
|
||||
homePageId={homePageId}
|
||||
switchDarkMode={switchDarkMode}
|
||||
isViewerSidebarPinned={isViewerSidebarPinned}
|
||||
setIsSidebarPinned={setIsSidebarPinned}
|
||||
darkMode={darkMode}
|
||||
canvasMaxWidth={canvasMaxWidth}
|
||||
canvasContentRef={canvasContentRef}
|
||||
currentMode={currentMode}
|
||||
isAppDarkMode={isAppDarkMode}
|
||||
mainCanvasContainer={mainCanvasContainer}
|
||||
canvasHeaderHeight={canvasHeaderHeight}
|
||||
/>
|
||||
)}
|
||||
</SuspenseCountProvider>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import React from 'react';
|
||||
import React, { Suspense, lazy } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
import { PAGE_CANVAS_HEADER_HEIGHT } from './appCanvasConstants';
|
||||
import PageCanvasHeader from './PageCanvasHeader';
|
||||
import PageCanvasFooter from './PageCanvasFooter';
|
||||
import PagesSidebarNavigation from './PageMenu/PagesSidebarNavigation';
|
||||
import { CanvasContentTail } from './CanvasContentTail';
|
||||
|
||||
const PageCanvasHeader = lazy(() => import('./PageCanvasHeader'));
|
||||
const PageCanvasFooter = lazy(() => import('./PageCanvasFooter'));
|
||||
|
||||
export const DesktopLayout = ({
|
||||
pageKey,
|
||||
isModuleMode,
|
||||
|
|
@ -35,7 +36,9 @@ export const DesktopLayout = ({
|
|||
className={cx({ 'h-100': isModuleMode })}
|
||||
style={{ position: 'relative', display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<PageCanvasHeader showCanvasHeader={showCanvasHeader} isMobileLayout={isMobileLayout} currentMode={currentMode} />
|
||||
<Suspense fallback={null}>
|
||||
<PageCanvasHeader showCanvasHeader={showCanvasHeader} isMobileLayout={isMobileLayout} currentMode={currentMode} />
|
||||
</Suspense>
|
||||
<div
|
||||
className={cx('canvas-wrapper tw-w-full tw-h-full d-flex', {
|
||||
'tw-flex-col': position === 'top' || isPagesSidebarHidden,
|
||||
|
|
@ -70,6 +73,8 @@ export const DesktopLayout = ({
|
|||
{mainCanvasContainer}
|
||||
</CanvasContentTail>
|
||||
</div>
|
||||
<PageCanvasFooter showCanvasFooter={showCanvasFooter} isMobileLayout={isMobileLayout} currentMode={currentMode} />
|
||||
<Suspense fallback={null}>
|
||||
<PageCanvasFooter showCanvasFooter={showCanvasFooter} isMobileLayout={isMobileLayout} currentMode={currentMode} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import React, { useRef } from 'react';
|
||||
import React, { Suspense, useRef, lazy } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
import { PAGE_CANVAS_HEADER_HEIGHT } from './appCanvasConstants';
|
||||
import PageCanvasHeader from './PageCanvasHeader';
|
||||
import PageCanvasFooter from './PageCanvasFooter';
|
||||
import MobileNavigationHeader from './PageMenu/MobileNavigationHeader';
|
||||
import { CanvasContentTail } from './CanvasContentTail';
|
||||
|
||||
const PageCanvasHeader = lazy(() => import('./PageCanvasHeader'));
|
||||
const PageCanvasFooter = lazy(() => import('./PageCanvasFooter'));
|
||||
|
||||
export const MobileLayout = ({
|
||||
pageKey,
|
||||
// mobileCanvasFrameRef,
|
||||
|
|
@ -41,7 +42,13 @@ export const MobileLayout = ({
|
|||
className={cx('tw-absolute tw-inset-0 tw-overflow-hidden tw-pointer-events-none')}
|
||||
/>
|
||||
{/* Canvas header — sticky at top of scroll */}
|
||||
<PageCanvasHeader showCanvasHeader={showCanvasHeader} isMobileLayout={isMobileLayout} currentMode={currentMode} />
|
||||
<Suspense fallback={null}>
|
||||
<PageCanvasHeader
|
||||
showCanvasHeader={showCanvasHeader}
|
||||
isMobileLayout={isMobileLayout}
|
||||
currentMode={currentMode}
|
||||
/>
|
||||
</Suspense>
|
||||
{/* Mobile nav — sticky below header */}
|
||||
{appType !== 'module' && (
|
||||
<div
|
||||
|
|
@ -65,7 +72,13 @@ export const MobileLayout = ({
|
|||
<CanvasContentTail currentMode={currentMode} appType={appType} isAppDarkMode={isAppDarkMode}>
|
||||
{mainCanvasContainer}
|
||||
</CanvasContentTail>
|
||||
<PageCanvasFooter showCanvasFooter={showCanvasFooter} isMobileLayout={isMobileLayout} currentMode={currentMode} />
|
||||
<Suspense fallback={null}>
|
||||
<PageCanvasFooter
|
||||
showCanvasFooter={showCanvasFooter}
|
||||
isMobileLayout={isMobileLayout}
|
||||
currentMode={currentMode}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
|
||||
|
||||
const AppLibrariesIcon = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default withEditionSpecificComponent(AppLibrariesIcon, 'AppLibraries');
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
|
||||
|
||||
const AppLibraries = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default withEditionSpecificComponent(AppLibraries, 'AppLibraries');
|
||||
|
|
@ -267,4 +267,5 @@
|
|||
width: 158px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import { useOthers, useSelf } from '@y-presence/react';
|
|||
import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore';
|
||||
import AppHistoryIcon from './AppHistory/AppHistoryIcon';
|
||||
import AppHistory from './AppHistory';
|
||||
import AppLibrariesIcon from './AppLibraries/AppLibrariesIcon';
|
||||
import AppLibraries from './AppLibraries';
|
||||
import { APP_HEADER_HEIGHT, QUERY_PANE_HEIGHT } from '../AppCanvas/appCanvasConstants';
|
||||
|
||||
// TODO: remove passing refs to LeftSidebarItem and use state
|
||||
|
|
@ -115,6 +117,8 @@ export const BaseLeftSidebar = ({
|
|||
return renderAIChat({ darkMode });
|
||||
case 'apphistory':
|
||||
return <AppHistory darkMode={darkMode} setPinned={setPinned} pinned={pinned} />;
|
||||
case 'libraries':
|
||||
return <AppLibraries darkMode={darkMode} onClose={() => toggleLeftSidebar(false)} />;
|
||||
case 'debugger':
|
||||
return <Debugger onClose={() => toggleLeftSidebar(false)} darkMode={darkMode} />;
|
||||
case 'settings':
|
||||
|
|
@ -189,6 +193,14 @@ export const BaseLeftSidebar = ({
|
|||
setSideBarBtnRefs={setSideBarBtnRefs}
|
||||
/>
|
||||
)}
|
||||
{featureAccess?.appJsLibraries && (
|
||||
<AppLibrariesIcon
|
||||
darkMode={darkMode}
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
handleSelectedSidebarItem={handleSelectedSidebarItem}
|
||||
setSideBarBtnRefs={setSideBarBtnRefs}
|
||||
/>
|
||||
)}
|
||||
<SidebarItem
|
||||
icon="settings"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
|
|
@ -218,7 +230,7 @@ export const BaseLeftSidebar = ({
|
|||
<Popover
|
||||
onInteractOutside={(e) => {
|
||||
// if tooljetai is open don't close
|
||||
if (['tooljetai', 'inspect', 'debugger', 'settings'].includes(selectedSidebarItem)) return;
|
||||
if (['tooljetai', 'inspect', 'debugger', 'settings', 'libraries'].includes(selectedSidebarItem)) return;
|
||||
const isWithinSidebar = e.target.closest('.left-sidebar');
|
||||
const isClickOnInspect = e.target.closest('.config-handle-inspect');
|
||||
if (pinned || isWithinSidebar || isClickOnInspect) return;
|
||||
|
|
|
|||
122
frontend/src/AppBuilder/_helpers/libraryLoader.js
Normal file
122
frontend/src/AppBuilder/_helpers/libraryLoader.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import toast from 'react-hot-toast';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* Executes UMD/IIFE source code and captures the exported module.
|
||||
* Creates a sandboxed environment with fake module/exports/define to capture
|
||||
* the library export without polluting the global scope.
|
||||
*/
|
||||
function executeUMD(source) {
|
||||
const module = { exports: {} };
|
||||
const exports = module.exports;
|
||||
let amdResult = null;
|
||||
|
||||
const define = Object.assign(
|
||||
(...args) => {
|
||||
// Handle: define(factory), define(deps, factory), define(id, deps, factory)
|
||||
const factory = args.find((a) => typeof a === 'function');
|
||||
if (factory) {
|
||||
amdResult = factory();
|
||||
return;
|
||||
}
|
||||
// Handle: define(value) — plain object/string export
|
||||
const value = args[args.length - 1];
|
||||
if (typeof value !== 'string') {
|
||||
amdResult = value;
|
||||
}
|
||||
},
|
||||
{ amd: true }
|
||||
);
|
||||
|
||||
const fn = new Function('module', 'exports', 'define', 'self', '"use strict";\n' + source);
|
||||
fn(module, exports, define, {});
|
||||
|
||||
// Priority: AMD result > reassigned module.exports > properties added to exports
|
||||
if (amdResult != null) return amdResult;
|
||||
if (module.exports !== exports) return module.exports; // was reassigned (e.g. module.exports = factory())
|
||||
if (Object.keys(exports).length > 0) return exports; // properties were added to original object
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a JS library from a URL and loads it using the UMD shim.
|
||||
* Returns the exported module object.
|
||||
*/
|
||||
export async function loadLibraryFromURL(url) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch library from ${url}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const source = await response.text();
|
||||
return executeUMD(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all JS libraries from the globalSettings.libraries.javascript array.
|
||||
* Returns a registry object mapping library names to their exports.
|
||||
*/
|
||||
export async function initializeLibraries(jsLibraries = []) {
|
||||
const registry = {};
|
||||
const enabledLibraries = jsLibraries.filter((lib) => lib.enabled);
|
||||
|
||||
// Load all libraries in parallel for faster startup
|
||||
const results = await Promise.allSettled(
|
||||
enabledLibraries.map(async (lib) => {
|
||||
const module = await loadLibraryFromURL(lib.url);
|
||||
return { name: lib.name, module };
|
||||
})
|
||||
);
|
||||
|
||||
results.forEach((result, index) => {
|
||||
const lib = enabledLibraries[index];
|
||||
if (result.status === 'fulfilled') {
|
||||
if (result.value.module != null) {
|
||||
registry[result.value.name] = result.value.module;
|
||||
} else {
|
||||
console.warn(`Library "${lib.name}" loaded but exported nothing. Ensure it's a UMD/IIFE build.`);
|
||||
}
|
||||
} else {
|
||||
console.error(`Failed to load library "${lib.name}" from ${lib.url}:`, result.reason);
|
||||
toast.error(`Failed to load JS library "${lib.name}"`);
|
||||
}
|
||||
});
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes preloaded JavaScript and captures exported functions/variables.
|
||||
* The code must return an object — each property becomes a top-level variable
|
||||
* available in RunJS queries, transformations, and {{}} expressions.
|
||||
*
|
||||
* Libraries (both built-in and user-added) are available in scope.
|
||||
* No access to components, queries, globals, etc.
|
||||
*
|
||||
* Example user code:
|
||||
* function formatCurrency(amount) { return '$' + amount.toFixed(2); }
|
||||
* const TAX_RATE = 0.08;
|
||||
* return { formatCurrency, TAX_RATE };
|
||||
*/
|
||||
export async function executePreloadedJS(code, libraryRegistry = {}) {
|
||||
if (!code?.trim()) return {};
|
||||
|
||||
try {
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
||||
const fnParams = ['moment', '_', 'axios', ...Object.keys(libraryRegistry)];
|
||||
const fnArgs = [moment, _, axios, ...Object.values(libraryRegistry)];
|
||||
const fn = new AsyncFunction(...fnParams, code);
|
||||
const result = await fn(...fnArgs);
|
||||
|
||||
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
console.error('Preloaded JS execution failed:', error);
|
||||
toast.error('Preloaded JavaScript failed: ' + error.message);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,8 @@ import { useLocation, useParams } from 'react-router-dom';
|
|||
import { useMounted } from '@/_hooks/use-mount';
|
||||
import useThemeAccess from './useThemeAccess';
|
||||
import toast from 'react-hot-toast';
|
||||
import { initializeLibraries, executePreloadedJS } from '@/AppBuilder/_helpers/libraryLoader';
|
||||
|
||||
/**
|
||||
* this is to normalize the query transformation options to match the expected schema. Takes care of corrupted data.
|
||||
* This will get redundanted once api response for appdata is made uniform across all the endpoints.
|
||||
|
|
@ -113,6 +115,8 @@ const useAppData = (
|
|||
const setPageSwitchInProgress = useStore((state) => state.setPageSwitchInProgress);
|
||||
const selectedVersion = useStore((state) => state.selectedVersion);
|
||||
const setIsPublicAccess = useStore((state) => state.setIsPublicAccess);
|
||||
const setJsLibraryRegistry = useStore((state) => state.setJsLibraryRegistry);
|
||||
const setJsLibraryLoading = useStore((state) => state.setJsLibraryLoading);
|
||||
|
||||
const setModulesIsLoading = useStore((state) => state?.setModulesIsLoading ?? noop);
|
||||
const setModulesList = useStore((state) => state?.setModulesList ?? noop);
|
||||
|
|
@ -596,10 +600,38 @@ const useAppData = (
|
|||
useEffect(() => {
|
||||
if (isComponentLayoutReady) {
|
||||
mode === 'edit' && initSuggestions(moduleId);
|
||||
runOnLoadQueries(moduleId).then(() => {
|
||||
|
||||
const loadLibrariesAndRun = async () => {
|
||||
// Load JS libraries and preloaded JS from globalSettings before running queries
|
||||
const globalSettings = useStore.getState().globalSettings;
|
||||
const jsLibraries = globalSettings?.libraries?.javascript || [];
|
||||
const preloadedJS = globalSettings?.preloadedScript?.javascript || '';
|
||||
|
||||
const hasJSLibrariesAccess = useStore.getState().license?.featureAccess?.appJsLibraries;
|
||||
|
||||
if (hasJSLibrariesAccess && (jsLibraries.length > 0 || preloadedJS)) {
|
||||
setJsLibraryLoading(true);
|
||||
try {
|
||||
const registry = jsLibraries.length > 0 ? await initializeLibraries(jsLibraries) : {};
|
||||
|
||||
// Execute preloaded JS — its returned exports merge into the registry
|
||||
const preloadedExports = await executePreloadedJS(preloadedJS, registry);
|
||||
const fullRegistry = { ...registry, ...preloadedExports };
|
||||
|
||||
setJsLibraryRegistry(fullRegistry);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize JS libraries:', error);
|
||||
} finally {
|
||||
setJsLibraryLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
await runOnLoadQueries(moduleId);
|
||||
const currentPageEvents = events.filter((event) => event.target === 'page' && event.sourceId === currentPageId);
|
||||
handleEvent('onPageLoad', currentPageEvents, {});
|
||||
});
|
||||
};
|
||||
|
||||
loadLibrariesAndRun();
|
||||
}
|
||||
}, [isComponentLayoutReady, moduleId, mode]);
|
||||
|
||||
|
|
|
|||
13
frontend/src/AppBuilder/_stores/slices/librarySlice.js
Normal file
13
frontend/src/AppBuilder/_stores/slices/librarySlice.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export const createLibrarySlice = (set, get) => ({
|
||||
jsLibraryRegistry: {},
|
||||
jsLibraryLoading: false,
|
||||
jsLibraryError: null,
|
||||
|
||||
setJsLibraryRegistry: (registry) => set(() => ({ jsLibraryRegistry: registry }), false, 'setJsLibraryRegistry'),
|
||||
|
||||
setJsLibraryLoading: (loading) => set(() => ({ jsLibraryLoading: loading }), false, 'setJsLibraryLoading'),
|
||||
|
||||
setJsLibraryError: (error) => set(() => ({ jsLibraryError: error }), false, 'setJsLibraryError'),
|
||||
|
||||
getJsLibraryRegistry: () => get().jsLibraryRegistry,
|
||||
});
|
||||
|
|
@ -748,6 +748,12 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
const { error } = e;
|
||||
const errorMessage = typeof error === 'string' ? error : error?.message || 'Unknown error';
|
||||
if (mode !== 'view') toast.error(errorMessage);
|
||||
const result = handleFailure({
|
||||
status: 'failed',
|
||||
message: errorMessage,
|
||||
data: e?.data || {},
|
||||
description: errorMessage,
|
||||
});
|
||||
resolve({ status: 'failed', message: errorMessage });
|
||||
});
|
||||
});
|
||||
|
|
@ -1220,6 +1226,9 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
const proxiedPage = deepClone(currentState?.page);
|
||||
const proxiedQueriesInResolvedState = queriesInResolvedState;
|
||||
|
||||
const hasJsLibrariesAccess = get().license?.featureAccess?.appJsLibraries;
|
||||
const libraryRegistry = hasJsLibrariesAccess ? get().jsLibraryRegistry || {} : {};
|
||||
|
||||
const evalFunction = Function(
|
||||
[
|
||||
'data',
|
||||
|
|
@ -1233,6 +1242,7 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
'constants',
|
||||
...(appType === 'module' ? ['input'] : []),
|
||||
'actions',
|
||||
...Object.keys(libraryRegistry),
|
||||
],
|
||||
transformation
|
||||
);
|
||||
|
|
@ -1258,7 +1268,8 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
log: function (log) {
|
||||
return actions.log.call(actions, log, true);
|
||||
},
|
||||
}
|
||||
},
|
||||
...Object.values(libraryRegistry)
|
||||
);
|
||||
} catch (err) {
|
||||
const stackLines = err.stack.split('\n');
|
||||
|
|
@ -1536,6 +1547,8 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
|
||||
try {
|
||||
const AsyncFunction = new Function(`return Object.getPrototypeOf(async function(){}).constructor`)();
|
||||
const hasJsLibrariesAccess = get().license?.featureAccess?.appJsLibraries;
|
||||
const libraryRegistry = hasJsLibrariesAccess ? get().jsLibraryRegistry || {} : {};
|
||||
const fnParams = [
|
||||
'moment',
|
||||
'_',
|
||||
|
|
@ -1549,6 +1562,7 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
'constants',
|
||||
...(!_.isEmpty(formattedParams) ? ['parameters'] : []), // Parameters are supported if builder has added atleast one parameter to the query
|
||||
...(appType === 'module' ? ['input'] : []), // Include 'input' only for module,
|
||||
...Object.keys(libraryRegistry),
|
||||
code,
|
||||
];
|
||||
var evalFn = new AsyncFunction(...fnParams);
|
||||
|
|
@ -1566,6 +1580,7 @@ export const createQueryPanelSlice = (set, get) => ({
|
|||
resolvedState?.constants,
|
||||
...(!_.isEmpty(formattedParams) ? [formattedParams] : []), // Parameters are supported if builder has added atleast one parameter to the query
|
||||
...(appType === 'module' ? [resolvedState.input] : []), // Include 'input' only for module
|
||||
...Object.values(libraryRegistry),
|
||||
];
|
||||
result = {
|
||||
status: 'ok',
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { createWhiteLabellingSlice } from './slices/whiteLabellingSlice';
|
|||
import { createFormComponentSlice } from './slices/componentSlices/formComponentSlice';
|
||||
import { createInspectorSlice } from './slices/inspectorSlice';
|
||||
import { createModuleSlice } from './slices/moduleSlice';
|
||||
import { createLibrarySlice } from './slices/librarySlice';
|
||||
import { createDataQueryFolderSlice } from './slices/dataQueryFolderSlice';
|
||||
import { listViewComponentSlice } from './slices/componentSlices/listViewComponentSlice';
|
||||
import { createBranchSlice } from './slices/branchSlice';
|
||||
|
|
@ -71,6 +72,7 @@ export default create(
|
|||
...createWhiteLabellingSlice(...state),
|
||||
...createInspectorSlice(...state),
|
||||
...createModuleSlice(...state),
|
||||
...createLibrarySlice(...state),
|
||||
...createDataQueryFolderSlice(...state),
|
||||
// component slices
|
||||
...createFormComponentSlice(...state),
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
"default": "no",
|
||||
"list": [
|
||||
{
|
||||
"name": "Basic Connection (No TNS/Wallet)",
|
||||
"name": "Basic Connection",
|
||||
"value": "no"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -597,6 +597,7 @@ export default class LicenseBase {
|
|||
scim: this.scim,
|
||||
observabilityEnabled: this.observabilityEnabled,
|
||||
appHistory: this.appHistory,
|
||||
appJsLibraries: this.appJsLibraries,
|
||||
queryFolders: this.queryFolders,
|
||||
workspaceEnv: this.workspaceEnv,
|
||||
aiPlan: this.aiPlan,
|
||||
|
|
@ -639,6 +640,7 @@ export default class LicenseBase {
|
|||
workflows: this.workflows,
|
||||
startDate: this.startDate,
|
||||
appHistoryEnabled: this.appHistory,
|
||||
appJsLibrariesEnabled: this.appJsLibraries,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -711,4 +713,15 @@ export default class LicenseBase {
|
|||
}
|
||||
return this._isEnvMapping;
|
||||
}
|
||||
|
||||
public get appJsLibraries(): boolean {
|
||||
if (this.IsBasicPlan) {
|
||||
return !!this.BASIC_PLAN_TERMS.app?.features?.jsLibraries;
|
||||
}
|
||||
|
||||
if (this._app?.features?.jsLibraries === undefined) {
|
||||
return false;
|
||||
}
|
||||
return !!this._app?.features?.jsLibraries;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export const BASIC_PLAN_TERMS: Partial<Terms> = {
|
|||
promote: false,
|
||||
release: false,
|
||||
history: false,
|
||||
jsLibraries: false,
|
||||
},
|
||||
components: {
|
||||
navigation: false,
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ export enum LICENSE_FIELD {
|
|||
AI_PLAN = 'aiPlan',
|
||||
EXTERNAL_API = 'externalApiEnabled',
|
||||
APP_HISTORY = 'appHistoryEnabled',
|
||||
APP_JS_LIBRARIES = 'appJsLibrariesEnabled',
|
||||
SCIM = 'scimEnabled',
|
||||
PLAN = 'plan',
|
||||
MODULES = 'modulesEnabled',
|
||||
|
|
|
|||
|
|
@ -172,6 +172,9 @@ export function getLicenseFieldValue(type: LICENSE_FIELD, licenseInstance: Licen
|
|||
case LICENSE_FIELD.QUERY_FOLDERS:
|
||||
return licenseInstance.queryFolders;
|
||||
|
||||
case LICENSE_FIELD.APP_JS_LIBRARIES:
|
||||
return licenseInstance.appJsLibraries;
|
||||
|
||||
default:
|
||||
return licenseInstance.terms;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export interface Terms {
|
|||
promote: boolean;
|
||||
release: boolean;
|
||||
history: boolean;
|
||||
jsLibraries: boolean;
|
||||
};
|
||||
components?: {
|
||||
navigation?: boolean;
|
||||
|
|
|
|||
Loading…
Reference in a new issue