Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configurable panel for console + ai assistant #1267

Merged
merged 22 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class HTMLCellsHandler {
private div?: HTMLDivElement;

attach(parent: HTMLDivElement) {
if (this.div) {
if (this.div && parent) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is breaking HMR, so I added this and now it works.

parent.appendChild(this.div);
}
}
Expand Down
260 changes: 183 additions & 77 deletions quadratic-client/src/ui/menus/CodeEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,33 @@ import { multiplayer } from '@/multiplayer/multiplayer';
import { Pos } from '@/quadratic-core/types';
import type { EvaluationResult } from '@/web-workers/pythonWebWorker/pythonTypes';
import mixpanel from 'mixpanel-browser';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
// TODO(ddimaria): leave this as we're looking to add this back in once improved
// import { Diagnostic } from 'vscode-languageserver-types';
import { Type } from '@/components/Type';
import { Button } from '@/shadcn/ui/button';
import { googleAnalyticsAvailable } from '@/utils/analytics';
import { AutoAwesome, TerminalOutlined, ViewStreamOutlined } from '@mui/icons-material';
import { hasPermissionToEditFile } from '../../../actions';
import { editorInteractionStateAtom } from '../../../atoms/editorInteractionStateAtom';
import { grid } from '../../../grid/controller/Grid';
import { pixiApp } from '../../../gridGL/pixiApp/PixiApp';
import { focusGrid } from '../../../helpers/focusGrid';
import { pythonWebWorker } from '../../../web-workers/pythonWebWorker/python';
import { AITab } from './AITab';
import './CodeEditor.css';
import { CodeEditorBody } from './CodeEditorBody';
import { CodeEditorProvider } from './CodeEditorContext';
import { CodeEditorHeader } from './CodeEditorHeader';
import { Console } from './Console';
import { Console, ConsoleOutput } from './Console';
import { ResizeControl } from './ResizeControl';
import { ReturnTypeInspector } from './ReturnTypeInspector';
import { SaveChangesAlert } from './SaveChangesAlert';

const CODE_EDITOR_MIN_WIDTH = 350;
const SECOND_PANEL_MIN_WIDTH = 300;

export const dispatchEditorAction = (name: string) => {
window.dispatchEvent(new CustomEvent('run-editor-action', { detail: name }));
};
Expand All @@ -33,6 +40,10 @@ export const CodeEditor = () => {
const [editorInteractionState, setEditorInteractionState] = useRecoilState(editorInteractionStateAtom);
const { showCodeEditor, mode: editorMode } = editorInteractionState;
const { pythonState } = useRecoilValue(pythonStateAtom);
const [secondPanelWidth, setSecondPanelWidth] = useState(SECOND_PANEL_MIN_WIDTH);
const [secondPanelHeightPercentage, setSecondPanelHeightPercentage] = useState<number>(50);
const containerRef = useRef<HTMLDivElement>(null);
const [panelPosition, setPanelPosition] = useState<PanelPosition>('left');

// update code cell
const [codeString, setCodeString] = useState('');
Expand Down Expand Up @@ -288,94 +299,189 @@ export const CodeEditor = () => {

return (
<CodeEditorProvider>
<div
id="QuadraticCodeEditorID"
style={{
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
display: 'flex',
flexDirection: 'column',
width: `${editorWidth}px`,
minWidth: '350px',
maxWidth: '90%',
backgroundColor: '#ffffff',
zIndex: 2,
}}
onKeyDownCapture={onKeyDownEditor}
onPointerEnter={() => {
// todo: handle multiplayer code editor here
multiplayer.sendMouseMove();
}}
onPointerMove={(e) => {
e.stopPropagation();
}}
>
{showSaveChangesAlert && (
<SaveChangesAlert
onCancel={() => {
setShowSaveChangesAlert(!showSaveChangesAlert);
setEditorInteractionState((old) => ({
...old,
editorEscapePressed: false,
waitingForEditorClose: undefined,
}));
}}
onSave={() => {
saveAndRunCell();
afterDialog();
}}
onDiscard={() => {
afterDialog();
}}
/>
)}
<div ref={containerRef}>
{panelPosition === 'left' && (
<div
className="absolute bottom-0 top-0 z-[2] flex flex-col bg-white"
style={{ right: `${editorWidth}px`, width: `${secondPanelWidth}px` }}
>
<PanelToggle position={panelPosition} setPosition={setPanelPosition} />
<ResizeControl
setState={(mouseEvent) => {
const offsetFromRight = window.innerWidth - mouseEvent.x - editorWidth;
setSecondPanelWidth(
offsetFromRight > SECOND_PANEL_MIN_WIDTH ? offsetFromRight : SECOND_PANEL_MIN_WIDTH
);
// const xPos = setState(min ? (newValue > min ? newValue : min) : newValue);
}}
position="LEFT"
min={200}
/>

<ResizeControl setState={setEditorWidth} position="LEFT" />
<CodeEditorHeader
cellLocation={cellLocation}
unsaved={unsaved}
saveAndRunCell={saveAndRunCell}
cancelPython={cancelPython}
closeEditor={() => closeEditor(false)}
/>
<CodeEditorBody
editorContent={editorContent}
setEditorContent={setEditorContent}
closeEditor={closeEditor}
evaluationResult={evaluationResult}
/>
{editorInteractionState.mode === 'Python' && (
<ReturnTypeInspector
evaluationResult={evaluationResult}
show={Boolean(evaluationResult?.line_number && !out?.stdErr && !unsaved)}
/>
<div style={{ height: `${secondPanelHeightPercentage}%` }}>
<div className="flex items-center gap-2 px-4 py-3">
<TerminalOutlined className="opacity-30" fontSize="small" />
<Type>Console</Type>
</div>
<div className="px-4 py-3">
<ConsoleOutput
consoleOutput={out}
editorMode={editorMode}
editorContent={editorContent}
evaluationResult={evaluationResult}
spillError={spillError}
/>
</div>
</div>
<ResizeControl
setState={(mouseEvent) => {
if (!containerRef.current) return;

const containerRect = containerRef.current?.getBoundingClientRect();
const newTopHeight = ((mouseEvent.clientY - containerRect.top) / containerRect.height) * 100;

if (newTopHeight >= 10 && newTopHeight <= 90) {
setSecondPanelHeightPercentage(newTopHeight);
}
}}
position="TOP"
/>
<div style={{ height: `${100 - secondPanelHeightPercentage}%` }}>
<div className="flex items-center gap-2 px-4 py-3">
<AutoAwesome className="opacity-30" fontSize="small" />
<Type>AI assistant</Type>
</div>
<div className="overflow-scroll px-4 py-3">
<AITab
// todo: fix this
evalResult={evaluationResult}
editorMode={editorMode}
editorContent={editorContent}
isActive={true}
/>
</div>
</div>
</div>
)}

<ResizeControl setState={setConsoleHeight} position="TOP" />
{/* Console Wrapper */}
<div
id="QuadraticCodeEditorID"
style={{
position: 'relative',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
display: 'flex',
flexDirection: 'column',
minHeight: '100px',
background: '#fff',
height: `${consoleHeight}px`,
width: `${editorWidth}px`,
// minWidth: '350px',
maxWidth: '90%',
backgroundColor: '#ffffff',
zIndex: 2,
}}
onKeyDownCapture={onKeyDownEditor}
onPointerEnter={() => {
// todo: handle multiplayer code editor here
multiplayer.sendMouseMove();
}}
onPointerMove={(e) => {
e.stopPropagation();
}}
>
{(editorInteractionState.mode === 'Python' || editorInteractionState.mode === 'Formula') && (
<Console
consoleOutput={out}
editorMode={editorMode}
editorContent={editorContent}
{showSaveChangesAlert && (
<SaveChangesAlert
onCancel={() => {
setShowSaveChangesAlert(!showSaveChangesAlert);
setEditorInteractionState((old) => ({
...old,
editorEscapePressed: false,
waitingForEditorClose: undefined,
}));
}}
onSave={() => {
saveAndRunCell();
afterDialog();
}}
onDiscard={() => {
afterDialog();
}}
/>
)}

<ResizeControl
setState={(mouseEvent) => {
const offsetFromRight = window.innerWidth - mouseEvent.x;
setEditorWidth(offsetFromRight > CODE_EDITOR_MIN_WIDTH ? offsetFromRight : CODE_EDITOR_MIN_WIDTH);
}}
position="LEFT"
min={CODE_EDITOR_MIN_WIDTH}
/>
<CodeEditorHeader
cellLocation={cellLocation}
unsaved={unsaved}
saveAndRunCell={saveAndRunCell}
cancelPython={cancelPython}
closeEditor={() => closeEditor(false)}
/>
<CodeEditorBody
editorContent={editorContent}
setEditorContent={setEditorContent}
closeEditor={closeEditor}
evaluationResult={evaluationResult}
/>
{editorInteractionState.mode === 'Python' && (
<ReturnTypeInspector
evaluationResult={evaluationResult}
spillError={spillError}
show={Boolean(evaluationResult?.line_number && !out?.stdErr && !unsaved)}
/>
)}

<ResizeControl setState={() => {}} position="TOP" />
{/* Console Wrapper */}
{panelPosition === 'bottom' && (
<div
style={{
position: 'relative',
display: 'flex',
flexDirection: 'column',
minHeight: '100px',
background: '#fff',
height: `${consoleHeight}px`,
}}
>
{(editorInteractionState.mode === 'Python' || editorInteractionState.mode === 'Formula') && (
<Console
consoleOutput={out}
editorMode={editorMode}
editorContent={editorContent}
evaluationResult={evaluationResult}
spillError={spillError}
/>
)}
<PanelToggle position={panelPosition} setPosition={setPanelPosition} />
</div>
)}
</div>
</div>
</CodeEditorProvider>
);
};

type PanelPosition = 'bottom' | 'left';
function PanelToggle({
position,
setPosition,
}: {
position: PanelPosition;
setPosition: React.Dispatch<React.SetStateAction<PanelPosition>>;
}) {
return (
<div className="absolute right-1 top-1">
<Button size="icon" variant={position === 'bottom' ? 'secondary' : 'ghost'} onClick={() => setPosition('bottom')}>
<ViewStreamOutlined className="text-foreground opacity-50" fontSize="small" style={{}} />
</Button>
<Button size="icon" variant={position === 'left' ? 'secondary' : 'ghost'} onClick={() => setPosition('left')}>
<ViewStreamOutlined className="rotate-90 opacity-50" fontSize="small" />
</Button>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export const CodeEditorHeader = (props: Props) => {
return (
<div
style={{
color: colors.darkGray,
fontSize: '0.875rem',
display: 'flex',
justifyContent: 'space-between',
Expand Down
Loading
Loading