From 1a9ecbbf646b485445941cb63b91f9b8b1b7bcdf Mon Sep 17 00:00:00 2001 From: Jim Nielsen Date: Thu, 6 Jul 2023 05:41:13 -1000 Subject: [PATCH] feat: better zero states around code (#541) --- src/ui/menus/CodeEditor/CodeEditor.tsx | 13 +- .../CodeEditor/CodeEditorPlaceholder.tsx | 137 +++++ src/ui/menus/CodeEditor/Console.tsx | 500 +++++++++--------- src/ui/menus/CodeEditor/styles.ts | 11 + 4 files changed, 412 insertions(+), 249 deletions(-) create mode 100644 src/ui/menus/CodeEditor/CodeEditorPlaceholder.tsx create mode 100644 src/ui/menus/CodeEditor/styles.ts diff --git a/src/ui/menus/CodeEditor/CodeEditor.tsx b/src/ui/menus/CodeEditor/CodeEditor.tsx index 8b27167771..a8f6017a7f 100644 --- a/src/ui/menus/CodeEditor/CodeEditor.tsx +++ b/src/ui/menus/CodeEditor/CodeEditor.tsx @@ -28,6 +28,7 @@ import { AI, Formula, Python } from '../../icons'; import { TooltipHint } from '../../components/TooltipHint'; import { KeyboardSymbols } from '../../../helpers/keyboardSymbols'; import { ResizeControl } from './ResizeControl'; +import { CodeEditorPlaceholder } from './CodeEditorPlaceholder'; import mixpanel from 'mixpanel-browser'; import useAlertOnUnsavedChanges from '../../../hooks/useAlertOnUnsavedChanges'; @@ -386,6 +387,7 @@ export const CodeEditor = (props: CodeEditorProps) => { {/* Editor Body */}
{ width="100%" language={editorMode === 'PYTHON' ? 'python' : editorMode === 'FORMULA' ? 'formula' : 'plaintext'} value={editorContent} - onChange={(value) => { - setEditorContent(value); - }} + onChange={setEditorContent} onMount={handleEditorDidMount} options={{ minimap: { enabled: true }, @@ -412,6 +412,13 @@ export const CodeEditor = (props: CodeEditorProps) => { wordWrap: 'on', }} /> + {selectedCell.type === 'PYTHON' && ( + + )}
diff --git a/src/ui/menus/CodeEditor/CodeEditorPlaceholder.tsx b/src/ui/menus/CodeEditor/CodeEditorPlaceholder.tsx new file mode 100644 index 0000000000..c026558ecf --- /dev/null +++ b/src/ui/menus/CodeEditor/CodeEditorPlaceholder.tsx @@ -0,0 +1,137 @@ +import { Fragment, RefObject, useEffect, useState } from 'react'; +import useLocalStorage from '../../../hooks/useLocalStorage'; +import { codeEditorBaseStyles, codeEditorCommentStyles } from './styles'; +import monaco from 'monaco-editor'; + +export const snippets = [ + { + label: 'fetch data', + // prettier-ignore + code: +`import json +from pyodide.http import pyfetch + +# Fetch data +res = await pyfetch( + 'https://jsonplaceholder.typicode.com/users', + method = 'GET', + headers = { + 'Content-Type': 'application/json' + } +) +users = json.loads(await res.string()) + +# Table +out = [] + +# Headers +out.append(['Username', 'Email', 'Website']) + +# Rows (from json) +for user in users: + out.append([user['username'], user['email'], user['website']]) + +# Last line returns to sheet +out`, + }, + { + label: 'reference cells', + // prettier-ignore + code: +`# Reference a value from the sheet +myCell = cell(x, y) + +# Or reference a range of cells (returns a Pandas DataFrame) +cells((x1, y1), (x2, y2))`, + }, + { + label: 'return data to the sheet', + // prettier-ignore + code: +`out = [] +for x in range(10): + out.append(x) + +# Last line returns to the sheet +out +# [out] # Wrap in array to expand horizontally`, + }, +]; + +export function CodeEditorPlaceholder({ + editorContent, + editorRef, + setEditorContent, +}: { + editorContent: string | undefined; + editorRef: RefObject; + setEditorContent: (str: string | undefined) => void; +}) { + const [showPlaceholder, setShowPlaceholder] = useLocalStorage('showCodeEditorPlaceholder', true); + const [shouldRunEffect, setShouldRunEffect] = useState(false); + + // When the user chooses to autofill the editor with a predefined snippet, + // focus the editor and set the initial cursor position + useEffect(() => { + if (editorRef && editorRef.current && shouldRunEffect) { + editorRef.current.focus(); + editorRef.current.setPosition({ lineNumber: 0, column: 0 }); + setShouldRunEffect(false); + } + }, [editorRef, editorContent, shouldRunEffect]); + + if (editorContent) { + return null; + } + + if (!showPlaceholder) { + return null; + } + + return ( +
+ Start with a code snippet to{' '} + {snippets.map((snippet, i: number) => ( + + { + e.preventDefault(); + setEditorContent(snippet.code); + setShouldRunEffect(true); + }} + > + {snippet.label} + + {i === snippets.length - 1 ? '.' : i < snippets.length - 2 ? ', ' : ', or '} + + ))} +
+
+ Start typing to dismiss or{' '} + { + e.preventDefault(); + setShowPlaceholder(false); + }} + > + don’t show this again + + . +
+ ); +} diff --git a/src/ui/menus/CodeEditor/Console.tsx b/src/ui/menus/CodeEditor/Console.tsx index 7b7e68cfa8..4857608cae 100644 --- a/src/ui/menus/CodeEditor/Console.tsx +++ b/src/ui/menus/CodeEditor/Console.tsx @@ -8,6 +8,7 @@ import { EditorInteractionState } from '../../../atoms/editorInteractionStateAto import { useTheme } from '@mui/system'; import { AITab } from './AITab'; import { useAuth0 } from '@auth0/auth0-react'; +import { codeEditorBaseStyles, codeEditorCommentStyles } from './styles'; interface ConsoleProps { editorMode: EditorInteractionState['mode']; @@ -81,17 +82,15 @@ export function Console({ evalResult, editorMode, editorContent }: ConsoleProps) }} style={{ outline: 'none', - fontFamily: 'monospace', - fontSize: '.875rem', - lineHeight: '1.3', whiteSpace: 'pre-wrap', + ...codeEditorBaseStyles, }} // Disable Grammarly data-gramm="false" data-gramm_editor="false" data-enable-grammarly="false" > - {hasOutput && ( + {hasOutput ? ( <> {std_err && ( @@ -100,284 +99,293 @@ export function Console({ evalResult, editorMode, editorContent }: ConsoleProps) )} {std_out} + ) : ( +
+ {editorMode === 'PYTHON' + ? 'Print statements, standard out, and errors will show here.' + : 'Errors will show here.'} +
)} - {editorMode === 'PYTHON' ? ( -
-

Logging

-

`print()` statements and errors are logged in the CONSOLE tab.

-

Returning data to the sheet

-

The last statement in your code is returned to the sheet.

-

Example:

-
-                1 2 *{' '}
-                2
-                
- ↳ 4 # number returned as the cell value -
-

Example:

-
-                1 result = []
-                
- 2 for x{' '} - in - range(100): -

- 3 {' '} - result.append(x) -
- 4 -
- 5 result -
- ↳ [0, 1, 2, ..., 99] # returns 100 cells counting from 0 to 99 -
+
+ {editorMode === 'PYTHON' ? ( +
+

Logging

+

`print()` statements and errors are logged in the CONSOLE tab.

+

Returning data to the sheet

+

The last statement in your code is returned to the sheet.

+

Example:

+
+                  1 2 *{' '}
+                  2
+                  
+ ↳ 4 # number returned as the cell value +
+

Example:

+
+                  1 result = []
+                  
+ 2 for x{' '} + in + range(100): +

+ 3 {' '} + result.append(x) +
+ 4 +
+ 5 result +
+ ↳ [0, 1, 2, ..., 99] # returns 100 cells counting from 0 to 99 +
-

Referencing data from the sheet

-

Use the `cell(x, y)` function — or shorthand `c(x, y)` — to reference values in the sheet.

-

Example:

-
-                1 c(1, 1) +{' '}
-                c(2, 2)
-                
- ↳ The sum of the cell values at x:1 y:1 and x:2 y:2 -
+

Referencing data from the sheet

+

Use the `cell(x, y)` function — or shorthand `c(x, y)` — to reference values in the sheet.

+

Example:

+
+                  1 c(1, 1) +{' '}
+                  c(2, 2)
+                  
+ ↳ The sum of the cell values at x:1 y:1 and x:2 y:2 +
-

Advanced topics

-
    -
  • Fetching data from an API.
  • -
  • Using Pandas DataFrames.
  • -
  • Installing third-party packages.
  • -
-

- Learn more in our documenation. -

-
-
- ) : editorMode === 'AI' ? ( - <> -

- -

- Warning: AI in Quadratic as a cell type is currently experimental.

The implementation may - change without notice. - -

Data generated by AI models needs to be validated as it is often incorrect. -
-

-

AI Docs

-
Generating New Data
-

- With GPT AI as a cell type, GPT AI can directly generate data and return it to the sheet. Whether you - need to generate a list of names, dates, or any other type of data, GPT AI can do it for you quickly and - easily, saving you valuable time and resources. -

-
Working With Existing Data
-

- When you use GPT AI as a cell type, it has access to the data in your sheet and can use it to generate - new data or update existing data. This means that GPT AI can analyze the data in your sheet and generate - new data that is consistent with the existing data. GPT AI can even generate data that is specific to - your needs, such as data that fits a particular pattern or meets certain criteria. With GPT AI support - in Quadratic, you can be confident that your data is always accurate and up-to-date. -

- - ) : ( - <> -

Spreadsheet formulas

-

Use the familiar language of spreadsheet formulas.

-

Example:

-
-                1 SUM(A0:A99)
+                

Advanced topics

+
    +
  • Fetching data from an API.
  • +
  • Using Pandas DataFrames.
  • +
  • Installing third-party packages.
  • +
+

+ Learn more in our documenation. +


- ↳ Returns the SUM of cells A0 to A99 -
-

Referencing cells

-

- In the positive quadrant, cells are referenced similar to other spreadsheets. In the negative quadrant, - cells are referenced using a `n` prefix. -

-

Examples:

- - - - + + +
-
- +
+ ) : editorMode === 'AI' ? ( + <> +

+ +

+ Warning: AI in Quadratic as a cell type is currently experimental.

The implementation may + change without notice. + +

Data generated by AI models needs to be validated as it is often incorrect. +
+

+

AI Docs

+
Generating New Data
+

+ With GPT AI as a cell type, GPT AI can directly generate data and return it to the sheet. Whether you + need to generate a list of names, dates, or any other type of data, GPT AI can do it for you quickly + and easily, saving you valuable time and resources. +

+
Working With Existing Data
+

+ When you use GPT AI as a cell type, it has access to the data in your sheet and can use it to generate + new data or update existing data. This means that GPT AI can analyze the data in your sheet and + generate new data that is consistent with the existing data. GPT AI can even generate data that is + specific to your needs, such as data that fits a particular pattern or meets certain criteria. With + GPT AI support in Quadratic, you can be confident that your data is always accurate and up-to-date. +

+ + ) : ( + <> +

Spreadsheet formulas

+

Use the familiar language of spreadsheet formulas.

+

Example:

+
+                  1 SUM(A0:A99)
+                  
+ ↳ Returns the SUM of cells A0 to A99 +
+

Referencing cells

+

+ In the positive quadrant, cells are referenced similar to other spreadsheets. In the negative + quadrant, cells are referenced using a `n` prefix. +

+

Examples:

+ + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - -
+
- nAn0 notation + + nAn0 notation + - -
-
-
- +
+
+
- - (x, y) - + + + (x, y) + + - -
-
-
- +
+
+
- A0 + + A0 + - -
-
-
- +
+
+
- (0, 0) + + (0, 0) + - -
-
-
- +
+
+
- A1 + + A1 + - -
-
-
- +
+
+
- (0, 1) + + (0, 1) + - -
-
-
- +
+
+
- B1 + + B1 + - -
-
-
- +
+
+
- (1, 1) + + (1, 1) + - -
-
-
- +
+
+
- An1 + + An1 + - -
-
-
- +
+
+
- (0, -1) + + (0, -1) + - -
-
-
- +
+
+
- nA1 + + nA1 + - -
-
-
- +
+
+
- (-1, 1) + + (-1, 1) + - -
-
-
- +
+
+
- nAn1 + + nAn1 + - -
-
-
- +
+
+
- (-1, -1) + + (-1, -1) + - -
-
-

Multiline formulas

-

- Line spaces are ignored when evaluating formulas. You can use them to make your formulas more readable. -

-

Example:

-
-                1 IF(A0 {'>'} 0,
-                

- 2   IF(B0 {'<'}{' '} - 2, -

- 3     "Valid Dataset", -

- 3     "B0 is invalid", + +
+

Multiline formulas

+

+ Line spaces are ignored when evaluating formulas. You can use them to make your formulas more + readable. +

+

Example:

+
+                  1 IF(A0 {'>'} 0,
+                  

+ 2   IF(B0{' '} + {'<'} 2, +

+ 3     "Valid Dataset", +

+ 3     "B0 is invalid", +

+ 4   ), +

+ 3   "A0 is invalid", +

+ 5 )

+
+

More info

+

+ Check out the docs to see a full list of + supported formulas and documentation for how to use specific formula functions. +



- 4   ), -

- 3   "A0 is invalid", -

- 5 )

- -

More info

-

- Check out the docs to see a full list of - supported formulas and documentation for how to use specific formula functions. -

-

- - )} + + )} +
diff --git a/src/ui/menus/CodeEditor/styles.ts b/src/ui/menus/CodeEditor/styles.ts new file mode 100644 index 0000000000..e984288be7 --- /dev/null +++ b/src/ui/menus/CodeEditor/styles.ts @@ -0,0 +1,11 @@ +export const codeEditorBaseStyles = { + fontFamily: 'Menlo, Monaco, "Courier New", monospace', + lineHeight: '18px', + fontSize: '12px', +}; + +// Kinda hacky, but works for now: replicate monaco styles elswhere +export const codeEditorCommentStyles = { + color: '#707780', + fontStyle: 'italic', +};