ToolJet/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx
NishidhJain11 fb7c751a34
Feat/AI modularisation (#13142)
* fix deps

* Modularisation changes for Build with AI feature

* New app loading UI for Build with AI feature & added animation for chat messages

* Fix Error using AI feature

* add missing services and logic

* fix app gen

* update submodules

* EE frontend submodule updated

* update submodules

* EE frontend submodule updated post sync

* Added Artifact Preview component to ee moddules list

* Updated ai slice code

* app gen changes

* Resolved fix with AI bugs

* Resolved AI Copilot bugs

* app gen changes and query fixes

* fix query generation bugs

* update copilot

* Resolved ChatMode dropdown & popover bug fix

* Resolved PR suggestions & PreviewBox component in CE edition

* Synced frontend/ee with main

* Synced server/ee with main branch

* Enhance submodule checkout process to handle branch existence and fallback to main (#13218)

* Enhance submodule checkout process to handle branch existence and fallback to main

* Improve submodule checkout process to handle branch validation and fallback to main

* chore: Comment out Node.js setup, dependency installation, and build steps in cloud frontend workflow

* refactor: Enhance submodule checkout process to include submodule name in logs

* Update submodule checkout process to use the correct submodule name extraction method

* fix: Update submodule checkout script to use correct submodule path variable

* Improve submodule checkout process to correctly handle branch names and fallback to main

* chore: Uncomment Node.js setup, dependency installation, and build steps in cloud frontend workflow

* fix: Update branch checkout logic to use correct syntax and improve fallback handling

* fix: Update git checkout command to use -B flag for branch creation

* fix: Improve submodule checkout process to explicitly fetch branch ref before checkout

* fix: Enhance submodule checkout process with improved branch validation and error handling

* fix: Improve branch checkout logic by enhancing fetch command and validating branch existence

* fix: Enhance manual Git checkout process with improved fetch and error handling

* fix: Restore Node.js setup, dependency installation, and Netlify deployment steps in workflow

* 🔄 chore: update submodules to latest main after auto-merge

* Took sync of fix/appbuilder-02 in frontend/ee

---------

Co-authored-by: Kartik Gupta <gupta.kartik18kg@gmail.com>
Co-authored-by: Adish M <44204658+adishM98@users.noreply.github.com>
Co-authored-by: adishM98 Bot <adish.madhu@gmail.com>
2025-07-07 15:11:58 +05:30

278 lines
9.6 KiB
JavaScript

import React, { useState, useEffect, useRef } from 'react';
import useStore from '@/AppBuilder/_stores/store';
import { SidebarItem } from './SidebarItem';
import cx from 'classnames';
import { shallow } from 'zustand/shallow';
import { DarkModeToggle } from '@/_components';
import Popover from '@/_ui/Popover';
// import { PageMenu } from './PageMenu';
import LeftSidebarInspector from './LeftSidebarInspector/LeftSidebarInspector';
import GlobalSettings from './GlobalSettings';
import '../../_styles/left-sidebar.scss';
import Debugger from './Debugger/Debugger';
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
import { PageMenu } from '../RightSideBar/PageSettingsTab/PageMenu';
// TODO: remove passing refs to LeftSidebarItem and use state
// TODO: need to add datasources to the sidebar.
// TODO: add dark/light mode toggle
// TODO: move popover and component selection to separate component
// TODO: create usable header component that can accept page specific buttton as props/children
export const BaseLeftSidebar = ({
darkMode = false,
switchDarkMode,
renderAISideBarTrigger = () => null,
renderAIChat = () => null,
isUserInZeroToOneFlow,
}) => {
const { moduleId, isModuleEditor, appType } = useModuleContext();
const [
pinned,
selectedSidebarItem,
setPinned,
setSelectedSidebarItem,
currentMode,
queryPanelHeight,
unreadErrorCount,
resetUnreadErrorCount,
toggleLeftSidebar,
isSidebarOpen,
isDraggingQueryPane,
] = useStore(
(state) => [
state.isLeftSideBarPinned,
state.selectedSidebarItem,
state.setIsLeftSideBarPinned,
state.setSelectedSidebarItem,
state.modeStore.modules[moduleId].currentMode,
state.queryPanel.queryPanelHeight,
state.debugger.unreadErrorCount,
state.debugger.resetUnreadErrorCount,
state.toggleLeftSidebar,
state.isSidebarOpen,
state.queryPanel.isDraggingQueryPane,
],
shallow
);
const [popoverContentHeight, setPopoverContentHeight] = useState(queryPanelHeight);
const sideBarBtnRefs = useRef({});
const handleSelectedSidebarItem = (item) => {
if (item === 'debugger') resetUnreadErrorCount();
setSelectedSidebarItem(item);
if (item === selectedSidebarItem && !pinned) {
return toggleLeftSidebar(false);
}
if (!isSidebarOpen) toggleLeftSidebar(true);
};
const setSideBarBtnRefs = (page) => (ref) => {
sideBarBtnRefs.current[page] = ref;
};
useEffect(() => {
if (isUserInZeroToOneFlow) {
setPopoverContentHeight(((window.innerHeight - 48) / window.innerHeight) * 100);
return;
}
if (!isDraggingQueryPane) {
setPopoverContentHeight(
((window.innerHeight - (queryPanelHeight == 0 ? 40 : queryPanelHeight) - 45) / window.innerHeight) * 100
);
} else {
setPopoverContentHeight(100);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isUserInZeroToOneFlow, queryPanelHeight, isDraggingQueryPane]);
const renderPopoverContent = () => {
if (selectedSidebarItem === null || !isSidebarOpen) return null;
switch (selectedSidebarItem) {
case 'page':
return (
<PageMenu
setPinned={setPinned}
pinned={pinned}
darkMode={darkMode}
selectedSidebarItem={selectedSidebarItem}
/>
);
case 'inspect':
return (
<LeftSidebarInspector
darkMode={darkMode}
// selectedSidebarItem={selectedSidebarItem}
// appDefinition={appDefinition}
// setSelectedComponent={setSelectedComponent}
// removeComponent={removeComponent}
// runQuery={runQuery}
// popoverContentHeight={popoverContentHeight}
setPinned={setPinned}
pinned={pinned}
moduleId={moduleId}
appType={appType}
/>
);
case 'tooljetai':
return renderAIChat({ darkMode, isUserInZeroToOneFlow });
// case 'datasource':
// return (
// <LeftSidebarDataSources
// darkMode={darkMode}
// appId={appId}
// dataSourcesChanged={dataSourcesChanged}
// globalDataSourcesChanged={globalDataSourcesChanged}
// dataQueriesChanged={dataQueriesChanged}
// toggleDataSourceManagerModal={toggleDataSourceManagerModal}
// showDataSourceManagerModal={showDataSourceManagerModal}
// onDeleteofAllDataSources={() => {
// handleSelectedSidebarItem(null);
// handlePin(false);
// delete sideBarBtnRefs.current['datasource'];
// }}
// setPinned={handlePin}
// pinned={pinned}
// />
// );
case 'debugger':
return <Debugger setPinned={setPinned} pinned={pinned} darkMode={darkMode} />;
// );
// case 'settings':
// return (
// <GlobalSettings
// globalSettingsChanged={globalSettingsChanged}
// globalSettings={appDefinition.globalSettings}
// darkMode={darkMode}
// toggleAppMaintenance={toggleAppMaintenance}
// isMaintenanceOn={isMaintenanceOn}
// app={app}
// backgroundFxQuery={backgroundFxQuery}
// />
// );
case 'settings':
return (
<GlobalSettings
// globalSettingsChanged={globalSettingsChanged}
// globalSettings={appDefinition.globalSettings}
darkMode={darkMode}
isModuleEditor={isModuleEditor}
// toggleAppMaintenance={toggleAppMaintenance}
// isMaintenanceOn={isMaintenanceOn}
// app={app}
// backgroundFxQuery={backgroundFxQuery}
/>
);
}
};
// TODO: Move this logic to a wrapper component and show components based on the mode
if (currentMode === 'view') {
return null;
}
const renderCommonItems = () => {
return (
<>
<SidebarItem
selectedSidebarItem={selectedSidebarItem}
onClick={() => handleSelectedSidebarItem('inspect')}
darkMode={darkMode}
icon="inspect"
className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`}
tip="Inspector"
ref={setSideBarBtnRefs('inspect')}
/>
<SidebarItem
icon="debugger"
selectedSidebarItem={selectedSidebarItem}
darkMode={darkMode}
// eslint-disable-next-line no-unused-vars
onClick={(e) => handleSelectedSidebarItem('debugger')}
className={`left-sidebar-item left-sidebar-layout`}
badge={true}
count={unreadErrorCount}
tip="Debugger"
ref={setSideBarBtnRefs('debugger')}
/>
</>
);
};
const renderLeftSidebarItems = () => {
if (isModuleEditor) {
return renderCommonItems();
}
return (
<>
{renderAISideBarTrigger({
selectedSidebarItem: selectedSidebarItem,
onClick: () => handleSelectedSidebarItem('tooljetai'),
darkMode: darkMode,
icon: 'tooljetai',
className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`,
tip: 'Build with AI',
ref: setSideBarBtnRefs('tooljetai'),
})}
{!isUserInZeroToOneFlow && (
<>
{renderCommonItems()}
<SidebarItem
icon="settings"
selectedSidebarItem={selectedSidebarItem}
darkMode={darkMode}
// eslint-disable-next-line no-unused-vars
onClick={(e) => handleSelectedSidebarItem('settings')}
className={`left-sidebar-item left-sidebar-layout`}
badge={true}
tip="Settings"
ref={setSideBarBtnRefs('settings')}
isModuleEditor={isModuleEditor}
/>
</>
)}
</>
);
};
return (
<div className={cx('left-sidebar', { 'dark-theme theme-dark': darkMode })} data-cy="left-sidebar-inspector">
{renderLeftSidebarItems()}
<Popover
onInteractOutside={(e) => {
// if tooljetai is open don't close
if (selectedSidebarItem === 'tooljetai') return;
const isWithinSidebar = e.target.closest('.left-sidebar');
const isClickOnInspect = e.target.closest('.config-handle-inspect');
if (pinned || isWithinSidebar || isClickOnInspect) return;
toggleLeftSidebar(false);
}}
open={isSidebarOpen}
popoverContentClassName={`p-0 left-sidebar-scrollbar sidebar-h-100-popover ${selectedSidebarItem}`}
side="right"
popoverContent={renderPopoverContent()}
popoverContentHeight={popoverContentHeight}
/>
<div className="left-sidebar-stack-bottom">
<div className="">
{/* <div style={{ maxHeight: '32px', maxWidth: '32px', marginBottom: '16px' }}>
<LeftSidebarComment
selectedSidebarItem={showComments ? 'comments' : ''}
currentPageId={currentPageId}
isVersionReleased={isVersionReleased}
isEditorFreezed={isEditorFreezed}
ref={setSideBarBtnRefs('comments')}
/>
</div> */}
<DarkModeToggle switchDarkMode={switchDarkMode} darkMode={darkMode} tooltipPlacement="right" />
</div>
</div>
</div>
);
};
export const LeftSidebar = withEditionSpecificComponent(BaseLeftSidebar, 'AiBuilder');