Skip to content

Commit

Permalink
Merge pull request #1582 from quadratichq/test-make-cells-async
Browse files Browse the repository at this point in the history
Test make cells async
  • Loading branch information
davidkircos authored Jul 23, 2024
2 parents f0b09e6 + 601e323 commit 14a7f3c
Show file tree
Hide file tree
Showing 24 changed files with 511 additions and 304 deletions.
7 changes: 7 additions & 0 deletions amplify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ frontend:
baseDirectory: build
files:
- '**/*'
customHeaders:
- pattern: '**/*'
headers:
- key: 'Cross-Origin-Opener-Policy'
value: 'same-origin'
- key: 'Cross-Origin-Embedder-Policy'
value: 'credentialless'
File renamed without changes.
26 changes: 13 additions & 13 deletions quadratic-client/src/app/ui/menus/CodeEditor/QuadraticDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1392,47 +1392,47 @@ In Quadratic, reference individual cells from JavaScript for single values or re
Referencing individual cells
To reference an individual cell, use the global function cell (or c for short) which returns the cell value.
// NOTE: cell is (x,y), so cell(2,3) means column 2, row 3
let data = await cell(2, 3);
let data = cell(2, 3);
return data;
You can reference cells and use them directly.
let data = await c(0, 0) + await c(0, 1) # Adds cell 0, 0 and cell 0, 1
let data = c(0, 0) + c(0, 1) # Adds cell 0, 0 and cell 0, 1
let data = await c(0, 0) == await c(0, 1) # Is cell 0, 0 equal to cell 0, 1 ?
let data = c(0, 0) == c(0, 1) # Is cell 0, 0 equal to cell 0, 1 ?
Any time cells dependent on other cells update the dependent cell will also update. This means your code will execute in one cell if it is dependent on another. This is the behavior you want in almost all situations, including user inputs in the sheet that cause calculation in a JavaScript cell.
Referencing a range of cells
To reference a range of cells, use the global function cells. This returns an array.
let data = await getCells(x1, y1, x2, y2)
let data = getCells(x1, y1, x2, y2)
Referencing another sheet
To reference another sheet's cells or range of cells use the following:
let data = await getCells(x1, y1, x2, y2, 'sheet_name')
let data = getCells(x1, y1, x2, y2, 'sheet_name')
Relative references
Reference cells relative to the cell you're currently in with relative cell references in JavaScript.
Get position of current cell
Keyword pos() returns the current cell's position.
# if the current position is cell (1,1) this would return an object with values 1,1
let cellPos = await pos();
let cellPos = pos();
Reference values in relative cells
Reference the values of cells relative the current position.
// c is the cell one cell to the left of the current cell, use either rel_cell or rc
let d = await rc(-1, 0);
let d = rc(-1, 0);
// above for one cell to the left is equivalent to the following
let cellPos = await pos();
let cellPos = pos();
let data = getCell(cellPos['x'] - 1, cellPos['y']);
// one cell left
let d = await rc(-1, 0);
let d = rc(-1, 0);
// one cell up
let d = await rc(0, -1);
let d = rc(0, -1);
// one cell right
let d = await rc(1, 0);
let d = rc(1, 0);
// one cell down
let d = await rc(0, 1);
let d = rc(0, 1);
// five cells left, five cells down
let d = await rc(-5, 5);
let d = rc(-5, 5);
return d;
Expand Down
12 changes: 6 additions & 6 deletions quadratic-client/src/app/ui/menus/CodeEditor/insertCellRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,25 @@ export const insertCellRef = (editorInteractionState: EditorInteractionState, re
const start = { x: multiCursor.left, y: multiCursor.top };
const end = { x: multiCursor.right - 1, y: multiCursor.bottom - 1 };
if (sheet) {
ref = `await cells(${start.x}, ${start.y}, ${end.x}, ${end.y}, '${sheet}')`;
ref = `cells(${start.x}, ${start.y}, ${end.x}, ${end.y}, '${sheet}')`;
} else {
if (relative) {
ref = `await relCells(${start.x - selectedCell.x}, ${start.y - selectedCell.y}, ${end.x - selectedCell.x}, ${
ref = `relCells(${start.x - selectedCell.x}, ${start.y - selectedCell.y}, ${end.x - selectedCell.x}, ${
end.y - selectedCell.y
})`;
} else {
ref = `await cells(${start.x}, ${start.y}, ${end.x}, ${end.y})`;
ref = `cells(${start.x}, ${start.y}, ${end.x}, ${end.y})`;
}
}
} else {
const location = cursor.cursorPosition;
if (sheet) {
ref = `await cell(${location.x}, ${location.y}, '${sheet}')`;
ref = `cell(${location.x}, ${location.y}, '${sheet}')`;
} else {
if (relative) {
ref = `await relCell(${location.x - selectedCell.x}, ${location.y - selectedCell.y})`;
ref = `relCell(${location.x - selectedCell.x}, ${location.y - selectedCell.y})`;
} else {
ref = `await cell(${location.x}, ${location.y})`;
ref = `cell(${location.x}, ${location.y})`;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ const snippets = [
{
label: 'Read data from the sheet',
keywords: 'reference cells',
code: `x1 = 0;
y1 = 0;
x2 = 0;
y2 = 0;
code: `let x1 = 0;
let y1 = 0;
let x2 = 0;
let y2 = 0;
// Reference a single value from the sheet
let single_cell_data = await cell(x1, y1);
let single_cell_data = cell(x1, y1);
// Reference a range of cells (returns an array)
let range_data = await cells(x1, y1, x2, y2);
let range_data = cells(x1, y1, x2, y2);
// Reference cell or range of cells in another sheet
let range_other_sheet_data = await cells(x1, y1, x2, y2, sheet_name);
let range_other_sheet_data = cells(x1, y1, x2, y2, 'Sheet 1');
// Returns the data to the sheet in a 2d array starting at the code cell
return range_data;`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import { javascriptClient } from '../javascriptClient';
import { JavascriptAPI } from './javascriptAPI';
import { javascriptFindSyntaxError, prepareJavascriptCode, transformCode } from './javascriptCompile';
import { javascriptErrorResult, javascriptResults } from './javascriptResults';
import { JavascriptRunnerGetCells, RunnerJavascriptMessage } from './javascriptRunnerMessages';
import { RunnerJavascriptMessage } from './javascriptRunnerMessages';
import { javascriptLibraryLines } from './runner/generateJavascriptForRunner';

export const LINE_NUMBER_VAR = '___line_number___';

export class Javascript {
private api: JavascriptAPI;
private awaitingExecution: CodeRun[];
private id = 0;
private getCellsResponses: Record<number, string> = {};

state: LanguageState = 'loading';

Expand Down Expand Up @@ -125,16 +127,42 @@ export class Javascript {
this.state = 'ready';
setTimeout(this.next, 0);
runner.terminate();
} else if (e.data.type === 'getCells') {
this.api.getCells(e.data.x0, e.data.y0, e.data.x1, e.data.y1, e.data.sheetName).then((results) => {
if (results) {
const message: JavascriptRunnerGetCells = results;
runner.postMessage(message);
} else if (e.data.type === 'getCellsLength') {
const { sharedBuffer, x0, y0, x1, y1, sheetName } = e.data;
this.api.getCells(x0, y0, x1, y1, sheetName).then((cells) => {
const int32View = new Int32Array(sharedBuffer, 0, 3);
if (cells) {
const cellsString = JSON.stringify(cells);
const length = cellsString.length;
Atomics.store(int32View, 1, length);
const id = this.id++;
this.getCellsResponses[id] = cellsString;
Atomics.store(int32View, 2, id);
Atomics.store(int32View, 0, 1);
Atomics.notify(int32View, 0, 1);
} else {
Atomics.store(int32View, 1, 0);
Atomics.store(int32View, 0, 1);
Atomics.notify(int32View, 0, 1);
this.state = 'ready';
setTimeout(this.next, 0);
}
});
} else if (e.data.type === 'getCellsData') {
const { id, sharedBuffer } = e.data;
const cells = this.getCellsResponses[id];
delete this.getCellsResponses[id];
const int32View = new Int32Array(sharedBuffer, 0, 1);
if (cells === undefined) {
console.error('[javascript] No cells found for id:', e.data.id);
} else {
const encoder = new TextEncoder();
const encodedCells = encoder.encode(cells);
const uint8View = new Uint8Array(e.data.sharedBuffer, 4, encodedCells.length);
uint8View.set(encodedCells);
}
Atomics.store(int32View, 0, 1);
Atomics.notify(int32View, 0, 1);
} else if (e.data.type === 'error') {
let errorLine: number | undefined;
let errorColumn: number | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function javascriptConvertOutputType(
message.push(
'WARNING: Unsupported output type: `Promise`' +
(x !== undefined && y !== undefined ? `at cell(${column + x}, ${row + y})` : '') +
'. Likely you are missing `await` before a call that returns a Promise, e.g., `await getCells(...)`.'
'. Likely you are missing `await` before a call that returns a Promise, e.g., `await fetch(...)`.'
);
return null;
} else if (typeof value === 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import { CellType } from './javascriptAPI';

export type JavascriptRunnerGetCells = CellType[][] | undefined;

export interface RunnerJavascriptGetCell {
type: 'getCells';
export interface RunnerJavascriptGetCellsLength {
type: 'getCellsLength';
sharedBuffer: SharedArrayBuffer;
x0: number;
y0: number;
x1: number;
y1: number;
sheetName?: string;
}

export interface RunnerJavascriptGetCellError {
type: 'getCellsError';
error: string;
export interface RunnerJavascriptGetCellsData {
type: 'getCellsData';
id: number;
sharedBuffer: SharedArrayBuffer;
}

export interface RunnerJavascriptResults {
Expand All @@ -33,6 +35,8 @@ export interface RunnerJavascriptError {
console: string;
}

export type JavascriptRunnerMessage = JavascriptRunnerGetCells;

export type RunnerJavascriptMessage = RunnerJavascriptGetCell | RunnerJavascriptResults | RunnerJavascriptError;
export type RunnerJavascriptMessage =
| RunnerJavascriptGetCellsLength
| RunnerJavascriptGetCellsData
| RunnerJavascriptResults
| RunnerJavascriptError;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated file from ./compileJavascriptRunner.mjs
export const javascriptLibrary = `const javascriptSendMessageAwaitingResponse=async message=>new Promise(resolve=>{self.onmessage=event=>resolve(event.data),self.postMessage(message)}),getCells=async(x0,y0,x1,y1,sheetName)=>{if(isNaN(x0)||isNaN(y0)||isNaN(x1))throw new Error("getCells requires at least 3 arguments, received getCells("+x0+", "+y0+", "+x1+", "+y1+")"+(___line_number___!==void 0?" at line "+___line_number___:""));return await javascriptSendMessageAwaitingResponse({type:"getCells",x0,y0,x1,y1,sheetName})},cells=getCells,getCellsWithHeadings=async(x0,y0,x1,y1,sheetName)=>{if(isNaN(x0)||isNaN(y0)||isNaN(x1))throw new Error("getCellsWithHeadings requires at least 3 arguments, received getCellsWithHeadings("+x0+", "+y0+", "+x1+", "+y1+")"+(___line_number___!==void 0?" at line "+___line_number___:""));const cells2=await getCells(x0,y0,x1,y1,sheetName),headers=cells2[0];return cells2.slice(1).map(row=>{const obj={};return headers.forEach((header,i)=>{obj[header]=row[i]}),obj})},getCell=async(x,y,sheetName)=>{if(isNaN(x)||isNaN(y))throw new Error("getCell requires at least 2 arguments, received getCell("+x+", "+y+")"+(___line_number___!==void 0?" at line "+___line_number___:""));return(await getCells(x,y,x,y,sheetName))?.[0]?.[0]},c=getCell,cell=getCell,pos=()=>({x:0,y:0}),relCell=async(deltaX,deltaY)=>{const p=pos();if(isNaN(deltaX)||isNaN(deltaY))throw new Error("relCell requires at least 2 arguments, received relCell("+deltaX+", "+deltaY+")"+(___line_number___!==void 0?" at line "+___line_number___:""));return await getCell(deltaX+p.x,deltaY+p.y)},relCells=async(deltaX0,deltaY0,deltaX1,deltaY1)=>{const p=pos();if(isNaN(deltaX0)||isNaN(deltaY0)||isNaN(deltaX1)||isNaN(deltaY1))throw new Error("relCells requires at least 4 arguments, received relCells("+deltaX0+", "+deltaY0+", "+deltaX1+", "+deltaY1+")"+(___line_number___!==void 0?" at line "+___line_number___:""));return await getCells(deltaX0+p.x,deltaY0+p.y,deltaX1+p.x,deltaY1+p.y)},rc=relCell,TAB=" ";class JavascriptConsole{oldConsoleLog;logs=[];constructor(){this.oldConsoleLog=console.log,console.log=this.consoleMap,console.warn=this.consoleMap}log(...args){this.oldConsoleLog(args)}consoleMap=(...args)=>{args=args.map(a=>this.mapArgument(a)),this.logs.push(...args)};reset(){this.logs=[]}push(s){Array.isArray(s)?this.logs.push(...s):this.logs.push(s)}output(){return this.logs.length?this.logs.join(""):null}tab=n=>Array(n).fill(TAB).join("");mapArgument(a,level=0){if(Array.isArray(a)){if(a.length===0)return"Array: []\\n";let s="Array: [\\n";for(let i=0;i<a.length;i++)s+=this.tab(level+1)+i+": "+this.mapArgument(a[i],level+2);return s+this.tab(level)+"]\\n"}else{if(a===null)return"null\\n";if(typeof a=="bigint")return a.toString()+"n\\n";if(a instanceof Date)return a.toString()+"\\n";if(typeof a=="object"){let s="Object: { \\n";for(const key in a)s+=this.tab(level+1)+key+": "+this.mapArgument(a[key],level+1);return s+this.tab(level)+"}\\n"}else return typeof a=="string"?a+"\\n":a===void 0?"undefined\\n":a+"\\n"}}}const javascriptConsole=new JavascriptConsole;export{c,cell,cells,getCell,getCells,getCellsWithHeadings,javascriptConsole,pos,rc,relCell,relCells};
export const javascriptLibrary = `const getCellsDB=(x0,y0,x1,y1,sheetName)=>{try{let sharedBuffer=new SharedArrayBuffer(12),int32View=new Int32Array(sharedBuffer,0,3);Atomics.store(int32View,0,0),self.postMessage({type:"getCellsLength",sharedBuffer,x0,y0,x1,y1,sheetName});let result=Atomics.wait(int32View,0,0);const length=int32View[1];if(result!=="ok"||length===0)return[];const id=int32View[2];if(sharedBuffer=new SharedArrayBuffer(4+length),int32View=new Int32Array(sharedBuffer,0,1),Atomics.store(int32View,0,0),self.postMessage({type:"getCellsData",id,sharedBuffer}),result=Atomics.wait(int32View,0,0),result!=="ok")return[];let uint8View=new Uint8Array(sharedBuffer,4,length);const nonSharedBuffer=new ArrayBuffer(uint8View.byteLength),nonSharedView=new Uint8Array(nonSharedBuffer);nonSharedView.set(uint8View),sharedBuffer=void 0,int32View=void 0,uint8View=void 0;const cellsStringified=new TextDecoder().decode(nonSharedView);return convertNullToUndefined(JSON.parse(cellsStringified))}catch(e){console.warn("[javascriptLibrary] getCells error",e)}return[]};function convertNullToUndefined(arr){return arr.map(subArr=>subArr.map(element=>element===null?void 0:element))}const getCells=(x0,y0,x1,y1,sheetName)=>{if(isNaN(x0)||isNaN(y0)||isNaN(x1))throw new Error("getCells requires at least 3 arguments, received getCells("+x0+", "+y0+", "+x1+", "+y1+")"+(___line_number___!==void 0?" at line "+___line_number___:""));return getCellsDB(x0,y0,x1,y1,sheetName)},cells=getCells,getCellsWithHeadings=(x0,y0,x1,y1,sheetName)=>{if(isNaN(x0)||isNaN(y0)||isNaN(x1))throw new Error("getCellsWithHeadings requires at least 3 arguments, received getCellsWithHeadings("+x0+", "+y0+", "+x1+", "+y1+")"+(___line_number___!==void 0?" at line "+___line_number___:""));const cells2=getCells(x0,y0,x1,y1,sheetName),headers=cells2[0];return cells2.slice(1).map(row=>{const obj={};return headers.forEach((header,i)=>{obj[header]=row[i]}),obj})},getCell=(x,y,sheetName)=>{if(isNaN(x)||isNaN(y))throw new Error("getCell requires at least 2 arguments, received getCell("+x+", "+y+")"+(___line_number___!==void 0?" at line "+___line_number___:""));return getCells(x,y,x,y,sheetName)?.[0]?.[0]},c=getCell,cell=getCell,pos=()=>({x:0,y:0}),relCell=(deltaX,deltaY)=>{const p=pos();if(isNaN(deltaX)||isNaN(deltaY))throw new Error("relCell requires at least 2 arguments, received relCell("+deltaX+", "+deltaY+")"+(___line_number___!==void 0?" at line "+___line_number___:""));return getCell(deltaX+p.x,deltaY+p.y)},relCells=(deltaX0,deltaY0,deltaX1,deltaY1)=>{const p=pos();if(isNaN(deltaX0)||isNaN(deltaY0)||isNaN(deltaX1)||isNaN(deltaY1))throw new Error("relCells requires at least 4 arguments, received relCells("+deltaX0+", "+deltaY0+", "+deltaX1+", "+deltaY1+")"+(___line_number___!==void 0?" at line "+___line_number___:""));return getCells(deltaX0+p.x,deltaY0+p.y,deltaX1+p.x,deltaY1+p.y)},rc=relCell,TAB=" ";class JavascriptConsole{oldConsoleLog;logs=[];constructor(){this.oldConsoleLog=console.log,console.log=this.consoleMap,console.warn=this.consoleMap}log(...args){this.oldConsoleLog(args)}consoleMap=(...args)=>{args=args.map(a=>this.mapArgument(a)),this.logs.push(...args)};reset(){this.logs=[]}push(s){Array.isArray(s)?this.logs.push(...s):this.logs.push(s)}output(){return this.logs.length?this.logs.join(""):null}tab=n=>Array(n).fill(TAB).join("");mapArgument(a,level=0){if(Array.isArray(a)){if(a.length===0)return"Array: []\\n";let s="Array: [\\n";for(let i=0;i<a.length;i++)s+=this.tab(level+1)+i+": "+this.mapArgument(a[i],level+2);return s+this.tab(level)+"]\\n"}else{if(a===null)return"null\\n";if(typeof a=="bigint")return a.toString()+"n\\n";if(a instanceof Date)return a.toString()+"\\n";if(typeof a=="object"){let s="Object: { \\n";for(const key in a)s+=this.tab(level+1)+key+": "+this.mapArgument(a[key],level+1);return s+this.tab(level)+"}\\n"}else return typeof a=="string"?a+"\\n":a===void 0?"undefined\\n":a+"\\n"}}}const javascriptConsole=new JavascriptConsole;export{c,cell,cells,getCell,getCells,getCellsWithHeadings,javascriptConsole,pos,rc,relCell,relCells};
`;
export const javascriptLibraryLines = javascriptLibrary.split("\n").length;
Loading

0 comments on commit 14a7f3c

Please sign in to comment.