From b6537cce6aa34eb33c052d7ec2cbbf804be08fba Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Wed, 28 Jun 2023 11:47:41 -0400 Subject: added files --- .../views/nodes/generativeFill/GenerativeFill.scss | 96 +++++++ .../views/nodes/generativeFill/GenerativeFill.tsx | 308 +++++++++++++++++++++ .../generativeFill/GenerativeFillButtons.scss | 4 + .../nodes/generativeFill/GenerativeFillButtons.tsx | 58 ++++ .../generativeFillUtils/BrushHandler.ts | 87 ++++++ .../GenerativeFillMathHelpers.ts | 11 + .../generativeFillUtils/ImageHandler.ts | 146 ++++++++++ .../generativeFillUtils/PointerHandler.ts | 15 + .../generativeFillUtils/generativeFillConstants.ts | 5 + .../generativeFillInterfaces.ts | 16 ++ 10 files changed, 746 insertions(+) create mode 100644 src/client/views/nodes/generativeFill/GenerativeFill.scss create mode 100644 src/client/views/nodes/generativeFill/GenerativeFill.tsx create mode 100644 src/client/views/nodes/generativeFill/GenerativeFillButtons.scss create mode 100644 src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx create mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts create mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts create mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts create mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts create mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts create mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts (limited to 'src') diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss new file mode 100644 index 000000000..92406ba9d --- /dev/null +++ b/src/client/views/nodes/generativeFill/GenerativeFill.scss @@ -0,0 +1,96 @@ +$navHeight: 5rem; +$canvasSize: 1024px; +$scale: 0.5; + +.generativeFillContainer { + position: absolute; + top: 0; + left: 0; + z-index: 999; + height: 100vh; + width: 100vw; + display: flex; + flex-direction: column; + overflow: hidden; + + .controls { + flex-shrink: 0; + height: $navHeight; + background-color: #ffffff; + z-index: 999; + width: 100%; + display: flex; + gap: 3rem; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #c7cdd0; + padding: 0 2rem; + + h1 { + font-size: 1.5rem; + } + } + + .drawingArea { + cursor: none; + touch-action: none; + position: relative; + flex-grow: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + background-color: #f0f4f6; + + canvas { + display: block; + position: absolute; + transform-origin: 50% 50%; + } + + .pointer { + pointer-events: none; + position: absolute; + border-radius: 50%; + width: 50px; + height: 50px; + border: 1px solid #ffffff; + transform: translate(-50%, -50%); + display: flex; + justify-content: center; + align-items: center; + + .innerPointer { + width: 100%; + height: 100%; + border: 1px solid #000000; + border-radius: 50%; + } + } + + .iconContainer { + position: absolute; + top: 2rem; + left: 2rem; + display: flex; + flex-direction: column; + gap: 2rem; + } + + .editsBox { + position: absolute; + top: 2rem; + right: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; + + img { + transition: all 0.2s ease-in-out; + &:hover { + opacity: 0.8; + } + } + } + } +} diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx new file mode 100644 index 000000000..6dd80a5d1 --- /dev/null +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -0,0 +1,308 @@ +import { useEffect, useRef, useState } from 'react'; +import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; +import { BrushHandler } from './generativeFillUtils/BrushHandler'; +import { IconButton, TextField } from '@mui/material'; +import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces'; +import { activeColor, canvasSize, eraserColor } from './generativeFillUtils/generativeFillConstants'; +import { PointerHandler } from './generativeFillUtils/PointerHandler'; +import { BsBrush, BsEraser } from 'react-icons/bs'; +import { AiOutlineUpload } from 'react-icons/ai'; +import { CiUndo, CiRedo } from 'react-icons/ci'; +import Buttons from './GenerativeFillButtons'; +import React from 'react'; +import './GenerativeFill.scss'; + +/** + * For images not 1024x1024 fill in the rest in solid black, or a + * reflected version of the image. + */ + +/** + * TODO: Look into img onload, sometimes the canvas doesn't update properly + */ + +enum BrushStyle { + ADD, + SUBTRACT, + MARQUEE, +} + +interface ImageEdit { + imgElement: HTMLImageElement; + parent: ImageEdit; + children: ImageEdit[]; +} + +const GenerativeFill = () => { + const canvasRef = useRef(null); + const canvasBackgroundRef = useRef(null); + const drawingAreaRef = useRef(null); + const fileRef = useRef(null); + const [cursorData, setCursorData] = useState({ + x: 0, + y: 0, + width: 150, + }); + const [isBrushing, setIsBrushing] = useState(false); + const [canvasScale, setCanvasScale] = useState(0.5); + const [edits, setEdits] = useState([]); + const [brushStyle, setBrushStyle] = useState(BrushStyle.ADD); + const [input, setInput] = useState(''); + const [loading, setLoading] = useState(false); + // used to store the current image loaded to the main canvas + const currImg = useRef(null); + // ref to store history + const undoStack = useRef([]); + + // initiate brushing + const handlePointerDown = (e: React.PointerEvent) => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = ImageUtility.getCanvasContext(canvasRef); + if (!ctx) return; + + setIsBrushing(true); + const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale); + + BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT); + }; + + // stop brushing, push to undo stack + const handlePointerUp = (e: React.PointerEvent) => { + const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef); + if (!ctx) return; + if (!isBrushing) return; + setIsBrushing(false); + }; + + // handles brushing on pointer movement + useEffect(() => { + if (!isBrushing) return; + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = ImageUtility.getCanvasContext(canvasRef); + if (!ctx) return; + + const handlePointerMove = (e: PointerEvent) => { + const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale); + const lastPoint: Point = { + x: currPoint.x - e.movementX / canvasScale, + y: currPoint.y - e.movementY / canvasScale, + }; + BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT); + }; + + drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove); + return () => drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove); + }, [isBrushing]); + + // first load + useEffect(() => { + const img = new Image(); + img.src = '/assets/art.jpeg'; + ImageUtility.drawImgToCanvas(img, canvasRef); + currImg.current = img; + }, [canvasRef]); + + useEffect(() => { + if (!canvasBackgroundRef.current) return; + const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef); + if (!ctx) return; + }, [canvasBackgroundRef]); + + // handles brush sizing + useEffect(() => { + const handleKeyPress = (e: KeyboardEvent) => { + if (e.key === 'ArrowUp') { + e.preventDefault(); + e.stopPropagation(); + setCursorData(data => ({ ...data, width: data.width + 5 })); + } else if (e.key === 'ArrowDown') { + e.preventDefault(); + e.stopPropagation(); + setCursorData(data => (data.width >= 20 ? { ...data, width: data.width - 5 } : data)); + } + }; + window.addEventListener('keydown', handleKeyPress); + return () => window.removeEventListener('keydown', handleKeyPress); + }, []); + + // handle pinch zoom + useEffect(() => { + const handlePinch = (e: WheelEvent) => { + e.preventDefault(); + e.stopPropagation(); + const delta = e.deltaY; + const scaleFactor = delta > 0 ? 0.98 : 1.02; // Adjust the scale factor as per your requirement + setCanvasScale(prevScale => prevScale * scaleFactor); + }; + + drawingAreaRef.current?.addEventListener('wheel', handlePinch, { + passive: false, + }); + return () => drawingAreaRef.current?.removeEventListener('wheel', handlePinch); + }, [drawingAreaRef]); + + // updates the current position of the cursor + const updateCursorData = (e: React.PointerEvent) => { + const drawingArea = drawingAreaRef.current; + if (!drawingArea) return; + const { x, y } = PointerHandler.getPointRelativeToElement(drawingArea, e, 1); + setCursorData(data => ({ + ...data, + x, + y, + })); + }; + + // File upload + const uploadImg = (e: React.ChangeEvent) => { + if (e.target.files) { + const file = e.target.files[0]; + const image = new Image(); + const imgUrl = URL.createObjectURL(file); + image.src = imgUrl; + ImageUtility.drawImgToCanvas(image, canvasRef); + currImg.current = image; + } + }; + + // Get AI Edit + const getEdit = async () => { + const img = currImg.current; + if (!img) return; + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = ImageUtility.getCanvasContext(canvasRef); + if (!ctx) return; + setLoading(true); + try { + const maskBlob = await ImageUtility.canvasToBlob(canvas); + const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img)); + + // const res = await ImageUtility.getEdit( + // imgBlob, + // maskBlob, + // input !== "" + // ? input + " in the same style" + // : "Fill in the image in the same style", + // 1 + // ); + + const res = await ImageUtility.mockGetEdit(); + const { urls } = res as APISuccess; + const image = new Image(); + image.src = urls[0]; + setLoading(false); + setEdits(urls); + ImageUtility.drawImgToCanvas(image, canvasRef); + currImg.current = image; + } catch (err) { + console.log(err); + } + }; + + return ( +
+
+

Generative Fill

+ +
+ {/* Main canvas for editing */} +
+ + +
+
+
+ {/* Icons */} +
+ + { + if (fileRef.current) { + fileRef.current.click(); + } + }}> + + + { + setBrushStyle(BrushStyle.ADD); + }}> + + + { + setBrushStyle(BrushStyle.SUBTRACT); + }}> + + + {/* Undo and Redo */} + {/* { + e.stopPropagation(); + console.log(undoStack.current); + }} + onPointerUp={e => { + e.stopPropagation(); + }}> + + + {}}> + + */} +
+ {/* Edits box */} +
+ {edits.map(edit => ( + { + const img = new Image(); + img.src = edit; + ImageUtility.drawImgToCanvas(img, canvasRef); + currImg.current = img; + }} + /> + ))} +
+
+
+ setInput(e.target.value)} + disabled={isBrushing} + type="text" + label="Prompt" + placeholder="Prompt..." + sx={{ + backgroundColor: '#ffffff', + position: 'absolute', + bottom: '1rem', + transform: 'translateX(calc(50vw - 50%))', + width: 'calc(100vw - 4rem)', + }} + /> +
+
+ ); +}; + +export default GenerativeFill; diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss b/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss new file mode 100644 index 000000000..0180ef904 --- /dev/null +++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss @@ -0,0 +1,4 @@ +.generativeFillBtnContainer { + display: flex; + gap: 1rem; +} diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx new file mode 100644 index 000000000..348e27a16 --- /dev/null +++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx @@ -0,0 +1,58 @@ +import { Button } from '@mui/material'; +import { ImageUtility } from './generativeFillUtils/ImageHandler'; +import { canvasSize } from './generativeFillUtils/generativeFillConstants'; +import { Oval } from 'react-loader-spinner'; +import './GenerativeFillButtons.scss'; +import React from 'react'; + +interface ButtonContainerProps { + canvasRef: React.RefObject; + backgroundref: React.RefObject; + currImg: React.MutableRefObject; + undoStack: React.MutableRefObject; + getEdit: () => Promise; + loading: boolean; +} + +const Buttons = ({ canvasRef, backgroundref, currImg, undoStack, loading, getEdit }: ButtonContainerProps) => { + const handleReset = () => { + if (!canvasRef.current || !currImg.current) return; + const ctx = ImageUtility.getCanvasContext(canvasRef); + if (!ctx) return; + ctx.clearRect(0, 0, canvasSize, canvasSize); + ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true); + }; + + return ( +
+ + + {/* */} + +
+ ); +}; + +export default Buttons; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts new file mode 100644 index 000000000..c2716e083 --- /dev/null +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts @@ -0,0 +1,87 @@ +import { GenerativeFillMathHelpers } from "./GenerativeFillMathHelpers"; +import { eraserColor } from "./generativeFillConstants"; +import { Point } from "./generativeFillInterfaces"; + +export class BrushHandler { + static brushCircle = ( + x: number, + y: number, + brushRadius: number, + ctx: CanvasRenderingContext2D + ) => { + ctx.globalCompositeOperation = "destination-out"; + ctx.shadowColor = "#ffffffeb"; + ctx.shadowBlur = 5; + ctx.beginPath(); + ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); + ctx.fill(); + ctx.closePath(); + }; + + static brushCircleOverlay = ( + x: number, + y: number, + brushRadius: number, + ctx: CanvasRenderingContext2D, + fillColor: string, + erase: boolean + ) => { + ctx.globalCompositeOperation = "destination-out"; + // ctx.globalCompositeOperation = erase ? "destination-out" : "source-over"; + ctx.fillStyle = fillColor; + ctx.shadowColor = eraserColor; + ctx.shadowBlur = 5; + ctx.beginPath(); + ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); + ctx.fill(); + ctx.closePath(); + }; + + static createBrushPath = ( + startPoint: Point, + endPoint: Point, + brushRadius: number, + ctx: CanvasRenderingContext2D + ) => { + const dist = GenerativeFillMathHelpers.distanceBetween( + startPoint, + endPoint + ); + + for (let i = 0; i < dist; i += 5) { + const s = i / dist; + BrushHandler.brushCircle( + startPoint.x * (1 - s) + endPoint.x * s, + startPoint.y * (1 - s) + endPoint.y * s, + brushRadius, + ctx + ); + } + }; + + static createBrushPathOverlay = ( + startPoint: Point, + endPoint: Point, + brushRadius: number, + ctx: CanvasRenderingContext2D, + fillColor: string, + erase: boolean + ) => { + const dist = GenerativeFillMathHelpers.distanceBetween( + startPoint, + endPoint + ); + + for (let i = 0; i < dist; i += 5) { + const s = i / dist; + BrushHandler.brushCircleOverlay( + startPoint.x * (1 - s) + endPoint.x * s, + startPoint.y * (1 - s) + endPoint.y * s, + brushRadius, + ctx, + fillColor, + erase + ); + } + }; +} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts new file mode 100644 index 000000000..027b99a52 --- /dev/null +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts @@ -0,0 +1,11 @@ +import { Point } from "./generativeFillInterfaces"; + +export class GenerativeFillMathHelpers { + // math helpers + static distanceBetween = (p1: Point, p2: Point) => { + return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); + }; + static angleBetween = (p1: Point, p2: Point) => { + return Math.atan2(p2.x - p1.x, p2.y - p1.y); + }; +} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts new file mode 100644 index 000000000..1c726afbb --- /dev/null +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -0,0 +1,146 @@ +import { RefObject } from "react"; +import { OPENAI_KEY } from "../keys"; +import { canvasSize } from "./generativeFillConstants"; + +export interface APISuccess { + status: "success"; + urls: string[]; +} + +export interface APIError { + status: "error"; + message: string; +} + +export class ImageUtility { + static canvasToBlob = (canvas: HTMLCanvasElement): Promise => { + return new Promise((resolve) => { + canvas.toBlob((blob) => { + if (blob) { + resolve(blob); + } + }, "image/png"); + }); + }; + + static getEdit = async ( + imgBlob: Blob, + maskBlob: Blob, + prompt: string, + n?: number + ): Promise => { + const apiUrl = "https://api.openai.com/v1/images/edits"; + const fd = new FormData(); + fd.append("image", imgBlob, "image.png"); + fd.append("mask", maskBlob, "mask.png"); + fd.append("prompt", prompt); + fd.append("size", "1024x1024"); + fd.append("n", n ? JSON.stringify(n) : "1"); + fd.append("response_format", "b64_json"); + + try { + const res = await fetch(apiUrl, { + method: "POST", + headers: { + Authorization: `Bearer ${OPENAI_KEY}`, + }, + body: fd, + }); + const data = await res.json(); + console.log(data.data); + return { + status: "success", + urls: (data.data as { b64_json: string }[]).map( + (data) => `data:image/png;base64,${data.b64_json}` + ), + }; + } catch (err) { + console.log(err); + return { status: "error", message: "API error." }; + } + }; + + static mockGetEdit = async (): Promise => { + return { + status: "success", + urls: [ + "/assets/shiba.png", + "/assets/souffle-dalle.png", + "/assets/firefly.png", + ], + }; + }; + + static getCanvasContext = ( + canvasRef: RefObject + ): CanvasRenderingContext2D | null => { + if (!canvasRef.current) return null; + const ctx = canvasRef.current.getContext("2d"); + if (!ctx) return null; + return ctx; + }; + + static downloadCanvas = (canvas: HTMLCanvasElement) => { + const url = canvas.toDataURL(); + const downloadLink = document.createElement("a"); + downloadLink.href = url; + downloadLink.download = "canvas"; + + downloadLink.click(); + downloadLink.remove(); + }; + + static downloadImageCanvas = (imgUrl: string) => { + const img = new Image(); + img.src = imgUrl; + img.onload = () => { + const canvas = document.createElement("canvas"); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext("2d"); + ctx?.drawImage(img, 0, 0, canvasSize, canvasSize); + + this.downloadCanvas(canvas); + }; + }; + + static drawImgToCanvas = ( + img: HTMLImageElement, + canvasRef: React.RefObject, + loaded?: boolean + ) => { + if (loaded) { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx) return; + ctx.globalCompositeOperation = "source-over"; + const scale = Math.min(canvasSize / img.width, canvasSize / img.height); + const width = img.width * scale; + const height = img.height * scale; + ctx.clearRect(0, 0, canvasSize, canvasSize); + ctx.drawImage(img, 0, 0, width, height); + } else { + img.onload = () => { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx) return; + ctx.globalCompositeOperation = "source-over"; + const scale = Math.min(canvasSize / img.width, canvasSize / img.height); + const width = img.width * scale; + const height = img.height * scale; + ctx.clearRect(0, 0, canvasSize, canvasSize); + ctx.drawImage(img, 0, 0, width, height); + }; + } + }; + + // The image must be loaded! + static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement => { + const canvas = document.createElement("canvas"); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext("2d"); + ctx?.clearRect(0, 0, canvasSize, canvasSize); + ctx?.drawImage(img, 0, 0, canvasSize, canvasSize); + + return canvas; + }; +} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts new file mode 100644 index 000000000..9e620ad11 --- /dev/null +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts @@ -0,0 +1,15 @@ +import { Point } from "./generativeFillInterfaces"; + +export class PointerHandler { + static getPointRelativeToElement = ( + element: HTMLElement, + e: React.PointerEvent | PointerEvent, + scale: number + ): Point => { + const boundingBox = element.getBoundingClientRect(); + return { + x: (e.clientX - boundingBox.x) / scale, + y: (e.clientY - boundingBox.y) / scale, + }; + }; +} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts new file mode 100644 index 000000000..78903b9aa --- /dev/null +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts @@ -0,0 +1,5 @@ +// constants +export const canvasSize = 1024; + +export const activeColor = "#1976d2"; +export const eraserColor = "#e1e9ec"; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts new file mode 100644 index 000000000..9b9b9d3c2 --- /dev/null +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts @@ -0,0 +1,16 @@ +// interfaces +export interface CursorData { + x: number; + y: number; + width: number; +} + +export interface Point { + x: number; + y: number; +} + +export enum BrushMode { + ADD, + SUBTRACT, +} -- cgit v1.2.3-70-g09d2 From 932ecd6092bd1b0ac3391309a550bac76cfb0e04 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 29 Jun 2023 13:56:41 -0400 Subject: undo and redo --- src/client/apis/gpt/GPT.ts | 2 +- src/client/views/MainView.tsx | 11 + src/client/views/nodes/ImageBox.tsx | 11 + .../views/nodes/formattedText/FormattedTextBox.tsx | 1 + .../views/nodes/generativeFill/GenerativeFill.scss | 4 +- .../views/nodes/generativeFill/GenerativeFill.tsx | 157 +++++++++++--- .../nodes/generativeFill/GenerativeFillButtons.tsx | 36 ++-- .../generativeFillUtils/ImageHandler.ts | 230 ++++++++++----------- .../generativeFillUtils/generativeFillConstants.ts | 5 +- 9 files changed, 273 insertions(+), 184 deletions(-) (limited to 'src') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 4b3960902..18222b32a 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -39,7 +39,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { temperature: opts.temp, prompt: `${opts.prompt}${inputText}`, }); - console.log(response.data.choices[0]); + // console.log(response.data.choices[0]); return response.data.choices[0].text; } catch (err) { console.log(err); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index c0c473cfc..1fa1eff84 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -65,6 +65,7 @@ import { PreviewCursor } from './PreviewCursor'; import { PropertiesView } from './PropertiesView'; import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider'; import { TopBar } from './topbar/TopBar'; +import GenerativeFill from './nodes/generativeFill/GenerativeFill'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -72,6 +73,15 @@ export class MainView extends React.Component { public static Instance: MainView; public static Live: boolean = false; private _docBtnRef = React.createRef(); + // for ai image editor + @observable public imageEditorOpen: boolean = false; + @action public setImageEditorOpen = (open: boolean) => (this.imageEditorOpen = open); + @observable public imageEditorSource: string = ''; + @action public setImageEditorSource = (source: string) => (this.imageEditorSource = source); + @observable public imageRootDoc: Doc | undefined; + @action public setImageRootDoc = (doc: Doc) => (this.imageRootDoc = doc); + @observable public addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined; + @observable public LastButton: Opt; @observable private _windowWidth: number = 0; @observable private _windowHeight: number = 0; @@ -1014,6 +1024,7 @@ export class MainView extends React.Component { {this.snapLines} + ); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 5b302e7ce..2c6f92681 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -38,6 +38,7 @@ import './ImageBox.scss'; import { PinProps, PresBox } from './trails'; import React = require('react'); import Color = require('color'); +import { MainView } from '../MainView'; export const pageSchema = createSchema({ googlePhotosUrl: 'string', @@ -248,6 +249,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(this.choosePath(field.url)), icon: 'copy' }); + funcs.push({ + description: 'Open Image Editor', + event: action(() => { + MainView.Instance.setImageEditorOpen(true); + MainView.Instance.setImageEditorSource(this.choosePath(field.url)); + MainView.Instance.addDoc = this.props.addDocument; + MainView.Instance.imageRootDoc = this.rootDoc; + }), + icon: 'pencil-alt', + }); if (!Doc.noviceMode) { funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0dc186eaf..3fa5c099b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -882,6 +882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + console.log(this.gptRes); if (resIndex < this.gptRes.length) { this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex]; setTimeout(() => { diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss index 92406ba9d..b1e570cf1 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.scss +++ b/src/client/views/nodes/generativeFill/GenerativeFill.scss @@ -6,14 +6,14 @@ $scale: 0.5; position: absolute; top: 0; left: 0; - z-index: 999; + z-index: 9999; height: 100vh; width: 100vw; display: flex; flex-direction: column; overflow: hidden; - .controls { + .generativeFillControls { flex-shrink: 0; height: $navHeight; background-color: #ffffff; diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 6dd80a5d1..0df05f4d7 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -3,14 +3,21 @@ import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { BrushHandler } from './generativeFillUtils/BrushHandler'; import { IconButton, TextField } from '@mui/material'; import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces'; -import { activeColor, canvasSize, eraserColor } from './generativeFillUtils/generativeFillConstants'; +import { activeColor, canvasSize, eraserColor, freeformRenderSize } from './generativeFillUtils/generativeFillConstants'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; -import { BsBrush, BsEraser } from 'react-icons/bs'; +import { BsBrush, BsEraser, BsX } from 'react-icons/bs'; import { AiOutlineUpload } from 'react-icons/ai'; import { CiUndo, CiRedo } from 'react-icons/ci'; import Buttons from './GenerativeFillButtons'; -import React from 'react'; +import React = require('react'); import './GenerativeFill.scss'; +import { EditableText } from 'browndash-components'; +import { MainView } from '../../MainView'; +import { Doc } from '../../../../fields/Doc'; +import { Networking } from '../../../Network'; +import { Utils } from '../../../../Utils'; +import { DocUtils, Docs } from '../../../documents/Documents'; +import { NumCast } from '../../../../fields/Types'; /** * For images not 1024x1024 fill in the rest in solid black, or a @@ -33,7 +40,14 @@ interface ImageEdit { children: ImageEdit[]; } -const GenerativeFill = () => { +interface GenerativeFillProps { + imageEditorOpen: boolean; + imageEditorSource: string; + imageRootDoc: Doc | undefined; + addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined; +} + +const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => { const canvasRef = useRef(null); const canvasBackgroundRef = useRef(null); const drawingAreaRef = useRef(null); @@ -51,8 +65,49 @@ const GenerativeFill = () => { const [loading, setLoading] = useState(false); // used to store the current image loaded to the main canvas const currImg = useRef(null); - // ref to store history - const undoStack = useRef([]); + const originalImg = useRef(null); + // stores history of data urls + const undoStack = useRef([]); + // stores redo stack + const redoStack = useRef([]); + + // Undo and Redo + const handleUndo = () => { + const ctx = ImageUtility.getCanvasContext(canvasRef); + if (!ctx || !currImg.current || !canvasRef.current) return; + + const target = undoStack.current[undoStack.current.length - 1]; + if (!target) { + ImageUtility.drawImgToCanvas(currImg.current, canvasRef); + } else { + redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()]; + const img = new Image(); + img.src = target; + ImageUtility.drawImgToCanvas(img, canvasRef); + undoStack.current = undoStack.current.slice(0, -1); + } + }; + + const handleRedo = () => { + // TODO: handle undo as well + const target = redoStack.current[redoStack.current.length - 1]; + if (!target) { + } else { + const img = new Image(); + img.src = target; + ImageUtility.drawImgToCanvas(img, canvasRef); + redoStack.current = redoStack.current.slice(0, -1); + } + }; + + const handleReset = () => { + if (!canvasRef.current || !currImg.current) return; + const ctx = ImageUtility.getCanvasContext(canvasRef); + if (!ctx) return; + ctx.clearRect(0, 0, canvasSize, canvasSize); + undoStack.current = []; + ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true); + }; // initiate brushing const handlePointerDown = (e: React.PointerEvent) => { @@ -61,6 +116,9 @@ const GenerativeFill = () => { const ctx = ImageUtility.getCanvasContext(canvasRef); if (!ctx) return; + undoStack.current = [...undoStack.current, canvasRef.current.toDataURL()]; + redoStack.current = []; + setIsBrushing(true); const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale); @@ -98,17 +156,13 @@ const GenerativeFill = () => { // first load useEffect(() => { + if (!imageEditorSource || imageEditorSource === '') return; const img = new Image(); - img.src = '/assets/art.jpeg'; + img.src = imageEditorSource; ImageUtility.drawImgToCanvas(img, canvasRef); currImg.current = img; - }, [canvasRef]); - - useEffect(() => { - if (!canvasBackgroundRef.current) return; - const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef); - if (!ctx) return; - }, [canvasBackgroundRef]); + originalImg.current = img; + }, [canvasRef, imageEditorSource]); // handles brush sizing useEffect(() => { @@ -180,16 +234,9 @@ const GenerativeFill = () => { const maskBlob = await ImageUtility.canvasToBlob(canvas); const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img)); - // const res = await ImageUtility.getEdit( - // imgBlob, - // maskBlob, - // input !== "" - // ? input + " in the same style" - // : "Fill in the image in the same style", - // 1 - // ); + const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2); - const res = await ImageUtility.mockGetEdit(); + // const res = await ImageUtility.mockGetEdit(); const { urls } = res as APISuccess; const image = new Image(); image.src = urls[0]; @@ -202,11 +249,47 @@ const GenerativeFill = () => { } }; + const onSave = async () => { + if (!currImg.current || !imageRootDoc) return; + try { + const src = currImg.current.src; + console.log(src); + const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] }); + const source = Utils.prepend(result.accessPaths.agnostic.client); + console.log(source); + const newImg = Docs.Create.ImageDocument(source, { + x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20, + y: NumCast(imageRootDoc.y), + _height: freeformRenderSize, + _width: freeformRenderSize, + data_nativeWidth: result.nativeWidth, + data_nativeHeight: result.nativeHeight, + }); + + addDoc?.(newImg); + // Create link between prompt and image + DocUtils.MakeLink(imageRootDoc, newImg, { link_relationship: 'Image Edit' }); + console.log('done'); + } catch (err) { + console.log(err); + } + }; + return ( -
-
+
+

Generative Fill

- +
+ + { + MainView.Instance.setImageEditorOpen(false); + MainView.Instance.setImageEditorSource(''); + setEdits([]); + }}> + + +
{/* Main canvas for editing */}
{ }}> - { setBrushStyle(BrushStyle.SUBTRACT); }}> - + */} {/* Undo and Redo */} - {/* { e.stopPropagation(); - console.log(undoStack.current); + handleUndo(); }} onPointerUp={e => { e.stopPropagation(); }}> - {}}> + { + e.stopPropagation(); + handleRedo(); + }} + onPointerUp={e => { + e.stopPropagation(); + }}> - */} +
{/* Edits box */}
@@ -292,12 +382,15 @@ const GenerativeFill = () => { type="text" label="Prompt" placeholder="Prompt..." + InputLabelProps={{ style: { fontSize: '1.5rem' } }} + inputProps={{ style: { fontSize: '1.5rem' } }} sx={{ backgroundColor: '#ffffff', position: 'absolute', bottom: '1rem', transform: 'translateX(calc(50vw - 50%))', width: 'calc(100vw - 4rem)', + scale: 1.2, }} />
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx index 348e27a16..e8cb61ab5 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx @@ -3,44 +3,34 @@ import { ImageUtility } from './generativeFillUtils/ImageHandler'; import { canvasSize } from './generativeFillUtils/generativeFillConstants'; import { Oval } from 'react-loader-spinner'; import './GenerativeFillButtons.scss'; -import React from 'react'; +import React = require('react'); +import { Doc } from '../../../../fields/Doc'; interface ButtonContainerProps { canvasRef: React.RefObject; - backgroundref: React.RefObject; currImg: React.MutableRefObject; - undoStack: React.MutableRefObject; getEdit: () => Promise; loading: boolean; + onSave: () => Promise; + onReset: () => void; } -const Buttons = ({ canvasRef, backgroundref, currImg, undoStack, loading, getEdit }: ButtonContainerProps) => { - const handleReset = () => { - if (!canvasRef.current || !currImg.current) return; - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx) return; - ctx.clearRect(0, 0, canvasSize, canvasSize); - ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true); +const Buttons = ({ canvasRef, currImg, loading, getEdit, onSave, onReset }: ButtonContainerProps) => { + const handleSave = () => { + onSave(); }; return (
- - + + {/* - {/* */} + Download Original + */}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index 91f12f866..48055903c 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -52,10 +52,10 @@ export class ImageUtility { } }; - static mockGetEdit = async (): Promise => { + static mockGetEdit = async (mockSrc: string): Promise => { return { status: 'success', - urls: ['/assets/shiba.png', '/assets/souffle-dalle.png', '/assets/firefly.png'], + urls: [mockSrc, mockSrc, mockSrc], }; }; -- cgit v1.2.3-70-g09d2 From f8d54bcc8cdb8d302be4ce3d5172ab59a1f574e7 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 6 Jul 2023 02:44:19 -0400 Subject: just saving progress --- .../CollectionFreeFormLinkView.tsx | 11 ++- .../views/nodes/generativeFill/GenerativeFill.tsx | 108 +++++++++++++++------ .../generativeFillUtils/generativeFillConstants.ts | 4 +- 3 files changed, 91 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index f1d98d22a..bdc0e1599 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -21,6 +21,8 @@ export interface CollectionFreeFormLinkViewProps { LinkDocs: Doc[]; } +// props.screentolocatransform + @observer export class CollectionFreeFormLinkView extends React.Component { @observable _opacity: number = 0; @@ -235,8 +237,12 @@ export class CollectionFreeFormLinkView extends React.Component diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index ab7e3dfef..5e7b4fdca 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -1,23 +1,25 @@ +import './GenerativeFill.scss'; +import React = require('react'); import { useEffect, useRef, useState } from 'react'; import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { BrushHandler } from './generativeFillUtils/BrushHandler'; import { IconButton, TextField } from '@mui/material'; import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces'; -import { activeColor, canvasSize, eraserColor, freeformRenderSize } from './generativeFillUtils/generativeFillConstants'; +import { activeColor, canvasSize, eraserColor, freeformRenderSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; import { BsBrush, BsEraser, BsX } from 'react-icons/bs'; import { AiOutlineUpload } from 'react-icons/ai'; import { CiUndo, CiRedo } from 'react-icons/ci'; import Buttons from './GenerativeFillButtons'; -import React = require('react'); -import './GenerativeFill.scss'; import { EditableText } from 'browndash-components'; import { MainView } from '../../MainView'; import { Doc } from '../../../../fields/Doc'; import { Networking } from '../../../Network'; import { Utils } from '../../../../Utils'; import { DocUtils, Docs } from '../../../documents/Documents'; -import { NumCast } from '../../../../fields/Types'; +import { DocCast, NumCast } from '../../../../fields/Types'; +import { CollectionDockingView } from '../../collections/CollectionDockingView'; +import { OpenWhere, OpenWhereMod } from '../DocumentView'; /** * For images not 1024x1024 fill in the rest in solid black, or a @@ -63,16 +65,23 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const [brushStyle, setBrushStyle] = useState(BrushStyle.ADD); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); - // used to store the current image loaded to the main canvas + // the current image in the main canvas const currImg = useRef(null); + // the unedited version of each generation (parent) const originalImg = useRef(null); // stores history of data urls const undoStack = useRef([]); // stores redo stack const redoStack = useRef([]); - // will change later, for now, stores an array [, ] + + // early stage properly, likely will get rid of const freeformPosition = useRef([0, 0]); + // references to keep track of tree structure + const newCollectionRef = useRef(null); + const parentDoc = useRef(null); + const childrenDocs = useRef([]); + // Undo and Redo const handleUndo = () => { const ctx = ImageUtility.getCanvasContext(canvasRef); @@ -165,6 +174,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD ImageUtility.drawImgToCanvas(img, canvasRef); currImg.current = img; originalImg.current = img; + freeformPosition.current = [0, 0]; }, [canvasRef, imageEditorSource]); // handles brush sizing @@ -237,9 +247,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const maskBlob = await ImageUtility.canvasToBlob(canvas); const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img)); - // const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2); - - const res = await ImageUtility.mockGetEdit(img.src); + const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2); + // const res = await ImageUtility.mockGetEdit(img.src); const { urls } = res as APISuccess; const image = new Image(); image.src = urls[0]; @@ -247,32 +256,73 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD setEdits(urls); ImageUtility.drawImgToCanvas(image, canvasRef); currImg.current = image; + freeformPosition.current[0] += 1; + freeformPosition.current[1] = 0; } catch (err) { console.log(err); } }; + // adjusts all the img positions to be aligned + const adjustImgPositions = () => { + if (!parentDoc.current) return; + const startY = NumCast(parentDoc.current.y); + const len = childrenDocs.current.length; + let initialYPositions: number[] = []; + for (let i = 0; i < len; i++) { + initialYPositions.push(startY + i * offsetDistanceY); + } + childrenDocs.current.forEach((doc, i) => { + if (len % 2 === 1) { + doc.y = initialYPositions[i] - Math.floor(len / 2) * offsetDistanceY; + } else { + doc.y = initialYPositions[i] - (len / 2 - 1 / 2) * offsetDistanceY; + } + }); + }; + + // creates a new image document and returns its reference + const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise => { + if (!newCollectionRef.current || !imageRootDoc || !parentDoc.current) return; + const src = img.src; + const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] }); + const source = Utils.prepend(result.accessPaths.agnostic.client); + // the y position is dummy + const x = firstDoc ? 0 : NumCast(parentDoc.current.x) + freeformRenderSize + offsetX; + const initialY = firstDoc ? 500 : 0; + + const newImg = Docs.Create.ImageDocument(source, { + x: x, + y: initialY, + _height: freeformRenderSize, + _width: freeformRenderSize, + data_nativeWidth: result.nativeWidth, + data_nativeHeight: result.nativeHeight, + }); + + childrenDocs.current.push(newImg); + adjustImgPositions(); + Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit' }); + return newImg; + }; + const onSave = async () => { - if (!currImg.current || !imageRootDoc) return; + if (!currImg.current || !originalImg.current || !imageRootDoc) return; try { - const src = currImg.current.src; - console.log(src); - const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] }); - const source = Utils.prepend(result.accessPaths.agnostic.client); - console.log(source); - const newImg = Docs.Create.ImageDocument(source, { - x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20, - y: NumCast(imageRootDoc.y), - _height: freeformRenderSize, - _width: freeformRenderSize, - data_nativeWidth: result.nativeWidth, - data_nativeHeight: result.nativeHeight, - }); - - addDoc?.(newImg); + if (!newCollectionRef.current) { + // create new collection and add it to the view + newCollectionRef.current = Docs.Create.FreeformDocument([], { x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 }); + addDoc?.(newCollectionRef.current); + const originalSrc = originalImg.current.src; + await createNewImgDoc(originalImg.current, true); + } + CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right); + // CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent) + // mindmapping + // this.props.addDocTab(); + // Create link between prompt and image - DocUtils.MakeLink(imageRootDoc, newImg, { link_relationship: 'Image Edit' }); - console.log('done'); } catch (err) { console.log(err); } @@ -361,9 +411,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
{/* Edits box */}
- {edits.map(edit => ( + {edits.map((edit, i) => ( Date: Thu, 6 Jul 2023 03:09:11 -0400 Subject: blind, need to test --- .../views/nodes/generativeFill/GenerativeFill.tsx | 40 +++++++++++++--------- .../nodes/generativeFill/GenerativeFillButtons.tsx | 2 +- 2 files changed, 25 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 5e7b4fdca..0ebf370c0 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -77,6 +77,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // early stage properly, likely will get rid of const freeformPosition = useRef([0, 0]); + // which urls were already saved to canvas + const savedSrcs = useRef([]); + // references to keep track of tree structure const newCollectionRef = useRef(null); const parentDoc = useRef(null); @@ -243,16 +246,18 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const ctx = ImageUtility.getCanvasContext(canvasRef); if (!ctx) return; setLoading(true); + // need to adjust later try { const maskBlob = await ImageUtility.canvasToBlob(canvas); const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img)); - const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2); // const res = await ImageUtility.mockGetEdit(img.src); + parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; + childrenDocs.current = []; + originalImg.current = currImg.current; const { urls } = res as APISuccess; const image = new Image(); image.src = urls[0]; - setLoading(false); setEdits(urls); ImageUtility.drawImgToCanvas(image, canvasRef); currImg.current = image; @@ -261,6 +266,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD } catch (err) { console.log(err); } + setLoading(false); }; // adjusts all the img positions to be aligned @@ -301,31 +307,34 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }); childrenDocs.current.push(newImg); - adjustImgPositions(); + if (!firstDoc) { + DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit', link_displayLine: true }); + adjustImgPositions(); + } Doc.AddDocToList(newCollectionRef.current, undefined, newImg); - DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit' }); return newImg; }; + // need to maybe call on every img click, not just when the save btn is clicked const onSave = async () => { + setLoading(true); if (!currImg.current || !originalImg.current || !imageRootDoc) return; try { if (!newCollectionRef.current) { // create new collection and add it to the view newCollectionRef.current = Docs.Create.FreeformDocument([], { x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 }); addDoc?.(newCollectionRef.current); - const originalSrc = originalImg.current.src; await createNewImgDoc(originalImg.current, true); } - CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right); + await createNewImgDoc(currImg.current, false); + // CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right); // CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent) - // mindmapping + // mind mapping // this.props.addDocTab(); - - // Create link between prompt and image } catch (err) { console.log(err); } + setLoading(false); }; return ( @@ -336,6 +345,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD { + if (newCollectionRef.current) { + CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + } MainView.Instance.setImageEditorOpen(false); MainView.Instance.setImageEditorSource(''); setEdits([]); @@ -381,12 +393,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }}> - {/* { - setBrushStyle(BrushStyle.SUBTRACT); - }}> - - */} {/* Undo and Redo */} { @@ -409,7 +415,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
- {/* Edits box */} + {/* Edits thumbnails*/}
{edits.map((edit, i) => ( ))} + {/* Original img thumbnail */} {edits.length > 0 && (
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 20a176c58..212d24165 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -909,7 +909,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { try { diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 07b2afd91..e6b9cb382 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -48,9 +48,6 @@ export class AnchorMenu extends AntimodeMenu { @observable public Status: 'marquee' | 'annotation' | '' = ''; // GPT additions - @observable private GPTpopupText: string = ''; - @observable private loadingGPT: boolean = false; - @observable private showGPTPopup: boolean = false; @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY; @observable private selectedText: string = ''; @observable private editorView?: EditorView; @@ -58,25 +55,11 @@ export class AnchorMenu extends AntimodeMenu { @observable private highlightRange: number[] | undefined; private selectionRange: number[] | undefined; - @action - setGPTPopupVis = (vis: boolean) => { - this.showGPTPopup = vis; - }; @action setGPTMode = (mode: GPTPopupMode) => { this.GPTMode = mode; }; - @action - setGPTPopupText = (txt: string) => { - this.GPTpopupText = txt; - }; - - @action - setLoading = (loading: boolean) => { - this.loadingGPT = loading; - }; - @action setHighlightRange(r: number[] | undefined) { this.highlightRange = r; @@ -130,19 +113,12 @@ export class AnchorMenu extends AntimodeMenu { componentDidMount() { this._disposer2 = reaction( () => this._opacity, - opacity => { - if (!opacity) { - this.setGPTPopupVis(false); - this.setGPTPopupText(''); - } - }, + opacity => {}, { fireImmediately: true } ); this._disposer = reaction( () => SelectionManager.Views().slice(), selected => { - this.setGPTPopupVis(false); - this.setGPTPopupText(''); AnchorMenu.Instance.fadeOut(true); } ); @@ -153,23 +129,22 @@ export class AnchorMenu extends AntimodeMenu { * @param e pointer down event */ gptSummarize = async (e: React.PointerEvent) => { + GPTPopup.Instance.setVisible(true); this.setHighlightRange(undefined); - this.setGPTPopupVis(true); this.setGPTMode(GPTPopupMode.SUMMARY); - this.setLoading(true); + GPTPopup.Instance.setLoading(true); try { const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY); if (res) { - this.setGPTPopupText(res); + GPTPopup.Instance.setText(res); } else { - this.setGPTPopupText('Something went wrong.'); + GPTPopup.Instance.setText('Something went wrong.'); } } catch (err) { console.error(err); } - - this.setLoading(false); + GPTPopup.Instance.setLoading(false); }; /** @@ -184,9 +159,9 @@ export class AnchorMenu extends AntimodeMenu { const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n'); const selectedText = state.doc.textBetween(sel.from, sel.to); - this.setGPTPopupVis(true); + GPTPopup.Instance.setVisible(true); this.setGPTMode(GPTPopupMode.EDIT); - this.setLoading(true); + GPTPopup.Instance.setLoading(true); try { let res = await gptAPICall(selectedText, GPTCallType.EDIT); @@ -196,16 +171,16 @@ export class AnchorMenu extends AntimodeMenu { const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1); if (res) { - this.setGPTPopupText(resultText); + GPTPopup.Instance.setText(resultText); this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]); } else { - this.setGPTPopupText('Something went wrong.'); + GPTPopup.Instance.setText('Something went wrong.'); } } catch (err) { console.error(err); } - this.setLoading(false); + GPTPopup.Instance.setLoading(false); }; /** @@ -253,21 +228,18 @@ export class AnchorMenu extends AntimodeMenu { }; @computed get highlighter() { - return - } - tooltip={'Click to Highlight'} - onClick={this.highlightClicked} - colorPicker={this.highlightColor} - color={StrCast(Doc.UserDoc().userColor)} - /> - this.changeHighlightColor(color)} - size={Size.XSMALL} - /> - + return ( + + } + tooltip={'Click to Highlight'} + onClick={this.highlightClicked} + colorPicker={this.highlightColor} + color={StrCast(Doc.UserDoc().userColor)} + /> + this.changeHighlightColor(color)} size={Size.XSMALL} /> + + ); } @action changeHighlightColor = (color: string) => { @@ -312,12 +284,7 @@ export class AnchorMenu extends AntimodeMenu { this.Status === 'marquee' ? ( <> {this.highlighter} - } - color={StrCast(Doc.UserDoc().userColor)} - /> + } color={StrCast(Doc.UserDoc().userColor)} /> {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( Summarize with AI
}> @@ -326,7 +293,7 @@ export class AnchorMenu extends AntimodeMenu { )} - { callEditApi={this.gptEdit} replaceText={this.replaceText} mode={this.GPTMode} - /> + /> */} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( - } - color={StrCast(Doc.UserDoc().userColor)} - /> - )} - {this.canEdit() && ( - } - color={StrCast(Doc.UserDoc().userColor)} - /> + } color={StrCast(Doc.UserDoc().userColor)} /> )} - } - popup={} - color={StrCast(Doc.UserDoc().userColor)} - /> + {this.canEdit() && } color={StrCast(Doc.UserDoc().userColor)} />} + } popup={} color={StrCast(Doc.UserDoc().userColor)} /> {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : ( - } - color={StrCast(Doc.UserDoc().userColor)} - /> + } color={StrCast(Doc.UserDoc().userColor)} /> )} ) : ( <> - {this.Delete !== returnFalse && } - color={StrCast(Doc.UserDoc().userColor)} - />} - {this.PinToPres !== returnFalse && } - color={StrCast(Doc.UserDoc().userColor)} - />} - {this.ShowTargetTrail !== returnFalse && } - color={StrCast(Doc.UserDoc().userColor)} - />} - {this.IsTargetToggler !== returnFalse && } - color={StrCast(Doc.UserDoc().userColor)} - />} + {this.Delete !== returnFalse && } color={StrCast(Doc.UserDoc().userColor)} />} + {this.PinToPres !== returnFalse && } color={StrCast(Doc.UserDoc().userColor)} />} + {this.ShowTargetTrail !== returnFalse && } color={StrCast(Doc.UserDoc().userColor)} />} + {this.IsTargetToggler !== returnFalse && ( + } + color={StrCast(Doc.UserDoc().userColor)} + /> + )} ); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 44413ede7..2f0ff83e2 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -6,12 +6,6 @@ $button: #5b97ff; $highlightedText: #82e0ff; .summary-box { - display: flex; - flex-direction: column; - justify-content: space-between; - background-color: #ffffff; - box-shadow: 0 2px 5px #7474748d; - color: $textgrey; position: fixed; bottom: 10px; right: 10px; @@ -21,6 +15,12 @@ $highlightedText: #82e0ff; padding: 15px; padding-bottom: 0; z-index: 999; + display: flex; + flex-direction: column; + justify-content: space-between; + background-color: #ffffff; + box-shadow: 0 2px 5px #7474748d; + color: $textgrey; .summary-heading { display: flex; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 8bd060d4f..9f28cb5d1 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -5,56 +5,96 @@ import { observer } from 'mobx-react'; import ReactLoading from 'react-loading'; import Typist from 'react-typist'; import { Doc } from '../../../../fields/Doc'; -import { Docs } from '../../../documents/Documents'; +import { DocUtils, Docs } from '../../../documents/Documents'; import './GPTPopup.scss'; +import { Button, IconButton } from 'browndash-components'; +import { StrCast } from '../../../../fields/Types'; export enum GPTPopupMode { SUMMARY, EDIT, } -interface GPTPopupProps { - visible: boolean; - text: string; - loading: boolean; - mode: GPTPopupMode; - callSummaryApi: (e: React.PointerEvent) => Promise; - callEditApi: (e: React.PointerEvent) => Promise; - replaceText: (replacement: string) => void; - highlightRange?: number[]; -} +interface GPTPopupProps {} @observer export class GPTPopup extends React.Component { static Instance: GPTPopup; @observable - private done: boolean = false; + public visible: boolean = false; + @action + public setVisible = (vis: boolean) => { + this.visible = vis; + }; @observable - private sidebarId: string = ''; + public loading: boolean = false; + @action + public setLoading = (loading: boolean) => { + this.loading = loading; + }; + @observable + public text: string = ''; + @action + public setText = (text: string) => { + this.text = text; + }; + @observable + public mode: GPTPopupMode = GPTPopupMode.SUMMARY; + @action + public setMode = (mode: GPTPopupMode) => { + this.mode = mode; + }; + @observable + public highlightRange: number[] = []; + @action callSummaryApi = () => {}; + @action callEditApi = () => {}; + @action replaceText = (replacement: string) => {}; + + @observable + private done: boolean = false; @action public setDone = (done: boolean) => { this.done = done; }; + @observable + private sidebarId: string = ''; @action public setSidebarId = (id: string) => { this.sidebarId = id; }; + // reference + // if (focusNode) { + // const anchor = srcWeb?.ComponentView?.getAnchor?.(true); + // anchor && DocUtils.MakeLink(htmlDoc, anchor, {}); + // } + @observable + private pdfAnchor: Doc | undefined; + @action + public setPdfAnchor = (anchor: Doc) => { + this.pdfAnchor = anchor; + }; + public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; /** * Transfers the summarization text to a sidebar annotation text document. */ private transferToText = () => { - const newDoc = Docs.Create.TextDocument(this.props.text.trim(), { + const newDoc = Docs.Create.TextDocument(this.text.trim(), { _width: 200, _height: 50, _layout_fitWidth: true, _layout_autoHeight: true, }); this.addDoc(newDoc, this.sidebarId); + if (this.pdfAnchor) { + DocUtils.MakeLink(newDoc, this.pdfAnchor, { + link_relationship: 'GPT Summary', + }); + } }; constructor(props: GPTPopupProps) { @@ -63,7 +103,7 @@ export class GPTPopup extends React.Component { } componentDidUpdate = () => { - if (this.props.loading) { + if (this.loading) { this.setDone(false); } }; @@ -73,10 +113,10 @@ export class GPTPopup extends React.Component {
{this.heading('SUMMARY')}
- {!this.props.loading && + {!this.loading && (!this.done ? ( { @@ -84,18 +124,18 @@ export class GPTPopup extends React.Component { this.setDone(true); }, 500); }}> - {this.props.text} + {this.text} ) : ( - this.props.text + this.text ))}
- {!this.props.loading && ( + {!this.loading && (
{this.done ? ( <> - + */} + } color={StrCast(Doc.UserDoc().userVariantColor)} /> + @@ -174,14 +216,14 @@ export class GPTPopup extends React.Component { heading = (headingText: string) => (
- {this.props.loading && } + {this.loading && }
); render() { return ( -
- {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()} +
+ {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()}
); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0fd93868a..4fc31ffe3 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -423,6 +423,11 @@ export class PDFViewer extends React.Component { // Changing which document to add the annotation to (the currently selected PDF) GPTPopup.Instance.setSidebarId('data_sidebar'); + const anchor = this._getAnchor(undefined, false); + if (anchor) { + console.log(anchor); + GPTPopup.Instance.setPdfAnchor(anchor); + } GPTPopup.Instance.addDoc = this.props.sidebarAddDoc; }; -- cgit v1.2.3-70-g09d2 From fec79d2b5b8feb361e489c9ee41ee720507d0806 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 27 Jul 2023 13:23:42 -0400 Subject: changes --- report.20230720.011057.28881.0.001.json | 1269 ++++++++++++++++++++ src/client/apis/gpt/GPT.ts | 28 +- src/client/util/DragManager.ts | 4 +- src/client/views/collections/TabDocView.tsx | 6 +- .../CollectionFreeFormLinkView.tsx | 8 +- src/client/views/global/globalScripts.ts | 50 +- src/client/views/nodes/WebBox.tsx | 5 + .../views/nodes/formattedText/FormattedTextBox.tsx | 89 +- src/client/views/pdf/AnchorMenu.tsx | 21 +- src/client/views/pdf/GPTPopup/GPTPopup.scss | 52 + src/client/views/pdf/GPTPopup/GPTPopup.tsx | 129 +- src/client/views/pdf/PDFViewer.tsx | 3 +- 12 files changed, 1530 insertions(+), 134 deletions(-) create mode 100644 report.20230720.011057.28881.0.001.json (limited to 'src') diff --git a/report.20230720.011057.28881.0.001.json b/report.20230720.011057.28881.0.001.json new file mode 100644 index 000000000..5c72c0c91 --- /dev/null +++ b/report.20230720.011057.28881.0.001.json @@ -0,0 +1,1269 @@ + +{ + "header": { + "reportVersion": 1, + "event": "Allocation failed - JavaScript heap out of memory", + "trigger": "FatalError", + "filename": "report.20230720.011057.28881.0.001.json", + "dumpEventTime": "2023-07-20T01:10:57Z", + "dumpEventTimeStamp": "1689829857721", + "processId": 28881, + "cwd": "/Users/smallwhale/Desktop/Projects/Dash-Web", + "commandLine": [ + "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node", + "--max-old-space-size=2048", + "/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/ts-node-dev/lib/wrap.js", + "/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/fork-ts-checker-webpack-plugin/lib/service.js" + ], + "nodejsVersion": "v12.16.0", + "wordSize": 64, + "arch": "x64", + "platform": "darwin", + "componentVersions": { + "node": "12.16.0", + "v8": "7.8.279.23-node.31", + "uv": "1.34.0", + "zlib": "1.2.11", + "brotli": "1.0.7", + "ares": "1.15.0", + "modules": "72", + "nghttp2": "1.40.0", + "napi": "5", + "llhttp": "2.0.4", + "http_parser": "2.9.3", + "openssl": "1.1.1d", + "cldr": "35.1", + "icu": "64.2", + "tz": "2019c", + "unicode": "12.1" + }, + "release": { + "name": "node", + "lts": "Erbium", + "headersUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0-headers.tar.gz", + "sourceUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0.tar.gz" + }, + "osName": "Darwin", + "osRelease": "22.4.0", + "osVersion": "Darwin Kernel Version 22.4.0: Mon Mar 6 21:01:02 PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T8112", + "osMachine": "x86_64", + "cpus": [ + { + "model": "Apple M2", + "speed": 2400, + "user": 40342390, + "nice": 0, + "sys": 32818540, + "idle": 263532590, + "irq": 0 + }, + { + "model": "Apple M2", + "speed": 2400, + "user": 37882510, + "nice": 0, + "sys": 28750670, + "idle": 270292020, + "irq": 0 + }, + { + "model": "Apple M2", + "speed": 2400, + "user": 32806560, + "nice": 0, + "sys": 24772270, + "idle": 279705870, + "irq": 0 + }, + { + "model": "Apple M2", + "speed": 2400, + "user": 28917140, + "nice": 0, + "sys": 21505900, + "idle": 287180070, + "irq": 0 + }, + { + "model": "Apple M2", + "speed": 2400, + "user": 52175300, + "nice": 0, + "sys": 11670040, + "idle": 274363170, + "irq": 0 + }, + { + "model": "Apple M2", + "speed": 2400, + "user": 30406640, + "nice": 0, + "sys": 8969580, + "idle": 298959950, + "irq": 0 + }, + { + "model": "Apple M2", + "speed": 2400, + "user": 21936370, + "nice": 0, + "sys": 6476860, + "idle": 310114030, + "irq": 0 + }, + { + "model": "Apple M2", + "speed": 2400, + "user": 16048180, + "nice": 0, + "sys": 4956530, + "idle": 317649610, + "irq": 0 + } + ], + "networkInterfaces": [ + { + "name": "lo0", + "internal": true, + "mac": "00:00:00:00:00:00", + "address": "127.0.0.1", + "netmask": "255.0.0.0", + "family": "IPv4" + }, + { + "name": "lo0", + "internal": true, + "mac": "00:00:00:00:00:00", + "address": "::1", + "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "family": "IPv6", + "scopeid": 0 + }, + { + "name": "lo0", + "internal": true, + "mac": "00:00:00:00:00:00", + "address": "fe80::1", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 1 + }, + { + "name": "anpi0", + "internal": false, + "mac": "42:10:d5:5b:93:9e", + "address": "fe80::4010:d5ff:fe5b:939e", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 4 + }, + { + "name": "anpi1", + "internal": false, + "mac": "42:10:d5:5b:93:9f", + "address": "fe80::4010:d5ff:fe5b:939f", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 5 + }, + { + "name": "en0", + "internal": false, + "mac": "9c:3e:53:8e:59:0e", + "address": "fe80::2b:dde:e859:46b8", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 12 + }, + { + "name": "en0", + "internal": false, + "mac": "9c:3e:53:8e:59:0e", + "address": "192.168.1.154", + "netmask": "255.255.255.0", + "family": "IPv4" + }, + { + "name": "en0", + "internal": false, + "mac": "9c:3e:53:8e:59:0e", + "address": "2600:4040:572e:2f00:18fe:e894:e23c:f4c2", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 0 + }, + { + "name": "en0", + "internal": false, + "mac": "9c:3e:53:8e:59:0e", + "address": "2600:4040:572e:2f00:c182:d0ad:6e64:1fae", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 0 + }, + { + "name": "awdl0", + "internal": false, + "mac": "b6:6c:9a:3c:a7:93", + "address": "fe80::b46c:9aff:fe3c:a793", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 13 + }, + { + "name": "llw0", + "internal": false, + "mac": "b6:6c:9a:3c:a7:93", + "address": "fe80::b46c:9aff:fe3c:a793", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 14 + }, + { + "name": "utun0", + "internal": false, + "mac": "00:00:00:00:00:00", + "address": "fe80::be27:76ec:8f11:5429", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 15 + }, + { + "name": "utun1", + "internal": false, + "mac": "00:00:00:00:00:00", + "address": "fe80::ce81:b1c:bd2c:69e", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 16 + }, + { + "name": "utun2", + "internal": false, + "mac": "00:00:00:00:00:00", + "address": "fe80::7707:8801:aef4:1907", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 17 + }, + { + "name": "utun3", + "internal": false, + "mac": "00:00:00:00:00:00", + "address": "fe80::a4f8:a3ce:851e:23dd", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 18 + }, + { + "name": "utun4", + "internal": false, + "mac": "00:00:00:00:00:00", + "address": "fe80::8d47:6641:b300:bde8", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 19 + }, + { + "name": "utun5", + "internal": false, + "mac": "00:00:00:00:00:00", + "address": "fe80::7c36:c665:d5f7:49d7", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 20 + }, + { + "name": "utun6", + "internal": false, + "mac": "00:00:00:00:00:00", + "address": "fe80::10d8:db7a:d714:7746", + "netmask": "ffff:ffff:ffff:ffff::", + "family": "IPv6", + "scopeid": 21 + } + ], + "host": "Sophies-MBP" + }, + "javascriptStack": { + "message": "No stack.", + "stack": [ + "Unavailable." + ] + }, + "nativeStack": [ + { + "pc": "0x000000010015c8ca", + "symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string, std::__1::allocator> const&, v8::Local) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x0000000100080f3e", + "symbol": "node::OnFatalError(char const*, char const*) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x0000000100185467", + "symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x0000000100185403", + "symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x000000010030b5f5", + "symbol": "v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x000000010030ccc4", + "symbol": "v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x0000000100309b37", + "symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x0000000100307afd", + "symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x00000001003132ba", + "symbol": "v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x0000000100313341", + "symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x00000001002e065b", + "symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x0000000100618a18", + "symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x0000000100950c19", + "symbol": "Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + }, + { + "pc": "0x00000001008d2291", + "symbol": "Builtins_FastNewObject [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]" + } + ], + "javascriptHeap": { + "totalMemory": 2152435712, + "totalCommittedMemory": 2150538320, + "usedMemory": 2139294040, + "availableMemory": 47735856, + "memoryLimit": 2197815296, + "heapSpaces": { + "read_only_space": { + "memorySize": 262144, + "committedMemory": 33088, + "capacity": 32808, + "used": 32808, + "available": 0 + }, + "new_space": { + "memorySize": 2097152, + "committedMemory": 1236968, + "capacity": 1047456, + "used": 203328, + "available": 844128 + }, + "old_space": { + "memorySize": 1956003840, + "committedMemory": 1955767472, + "capacity": 1948209048, + "used": 1947744360, + "available": 464688 + }, + "code_space": { + "memorySize": 14061568, + "committedMemory": 13610624, + "capacity": 12121760, + "used": 12121760, + "available": 0 + }, + "map_space": { + "memorySize": 1576960, + "committedMemory": 1456120, + "capacity": 1257120, + "used": 1257120, + "available": 0 + }, + "large_object_space": { + "memorySize": 178384896, + "committedMemory": 178384896, + "capacity": 177931880, + "used": 177931880, + "available": 0 + }, + "code_large_object_space": { + "memorySize": 49152, + "committedMemory": 49152, + "capacity": 2784, + "used": 2784, + "available": 0 + }, + "new_large_object_space": { + "memorySize": 0, + "committedMemory": 0, + "capacity": 1047456, + "used": 0, + "available": 1047456 + } + } + }, + "resourceUsage": { + "userCpuSeconds": 312.946, + "kernelCpuSeconds": 137.981, + "cpuConsumptionPercent": 62.0257, + "maxRss": 2062897119232, + "pageFaults": { + "IORequired": 658, + "IONotRequired": 43579208 + }, + "fsActivity": { + "reads": 0, + "writes": 0 + } + }, + "libuv": [ + ], + "environmentVariables": { + "npm_config_save_dev": "", + "npm_config_legacy_bundling": "", + "npm_config_dry_run": "", + "npm_package_dependencies_translate_google_api": "^1.0.4", + "npm_package_dependencies_request": "^2.88.2", + "npm_package_dependencies_express_flash": "0.0.2", + "npm_package_dependencies__fortawesome_fontawesome_svg_core": "^6.3.0", + "NVM_INC": "/Users/smallwhale/.nvm/versions/node/v12.16.0/include/node", + "npm_config_viewer": "man", + "npm_config_only": "", + "npm_config_commit_hooks": "true", + "npm_config_browser": "", + "npm_package_gitHead": "e11aa60b774d457cb016bb0f375ce092f0a733af", + "npm_package_dependencies_webpack_dev_middleware": "^5.3.1", + "npm_package_dependencies_webpack_cli": "^4.10.0", + "npm_package_devDependencies_prettier": "^2.7.1", + "npm_package_devDependencies_awesome_typescript_loader": "^5.2.1", + "npm_package_devDependencies__types_archiver": "^3.1.1", + "MANPATH": "/Users/smallwhale/.nvm/versions/node/v12.16.0/share/man:/Users/smallwhale/.nvm/versions/node/v18.16.0/share/man:/opt/homebrew/share/man:/usr/share/man:/usr/local/share/man:/Users/smallwhale/.nvm/versions/node/v18.16.0/share/man:/opt/homebrew/share/man::", + "npm_config_also": "", + "npm_package_dependencies_react_jsx_parser": "^1.29.0", + "npm_package_dependencies_mongoose": "^5.13.14", + "npm_package_dependencies_connect_flash": "^0.1.1", + "npm_package_browser_child_process": "false", + "npm_config_sign_git_commit": "", + "npm_config_rollback": "true", + "npm_package_dependencies_material_ui": "^0.20.2", + "npm_package_devDependencies__types_sharp": "^0.23.1", + "npm_package_devDependencies__types_passport_local": "^1.0.34", + "npm_package_devDependencies__types_dotenv": "^6.1.1", + "npm_package_devDependencies__types_cookie_parser": "^1.4.2", + "TERM_PROGRAM": "vscode", + "NODE": "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node", + "npm_config_usage": "", + "npm_config_audit": "true", + "npm_package_dependencies_reveal_js": "^4.3.0", + "npm_package_dependencies_process": "^0.11.10", + "npm_package_dependencies_pdfjs": "^2.4.7", + "npm_package_dependencies_html_to_image": "^0.1.3", + "npm_package_devDependencies_file_loader": "^3.0.1", + "npm_package_devDependencies__types_express_flash": "0.0.0", + "npm_package_scripts_monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts", + "INIT_CWD": "/Users/smallwhale/Desktop/Projects/Dash-Web", + "npm_package_dependencies_rehype_raw": "^6.1.1", + "npm_package_dependencies_react_audio_waveform": "0.0.5", + "npm_package_dependencies_path_browserify": "^1.0.1", + "npm_package_dependencies_nodemailer": "^5.1.1", + "npm_package_dependencies_axios": "^0.19.2", + "npm_package_devDependencies_typescript": "^4.7.4", + "NVM_CD_FLAGS": "-q", + "npm_config_globalignorefile": "/Users/smallwhale/.nvm/versions/node/v12.16.0/etc/npmignore", + "npm_package_dependencies_react_grid_layout": "^1.3.4", + "npm_package_dependencies_prosemirror_find_replace": "^0.9.0", + "npm_package_dependencies_normalize_css": "^8.0.1", + "npm_package_devDependencies_mocha": "^5.2.0", + "npm_package_devDependencies__types_express_session": "^1.17.5", + "TERM": "xterm-256color", + "SHELL": "/bin/zsh", + "npm_config_shell": "/bin/zsh", + "npm_config_maxsockets": "50", + "npm_config_init_author_url": "", + "npm_package_dependencies_prosemirror_dev_tools": "^3.1.0", + "npm_package_dependencies_p_limit": "^2.2.0", + "npm_package_dependencies_bson": "^4.6.1", + "npm_package_dependencies__types_dom_speech_recognition": "0.0.1", + "npm_package_dependencies__emotion_styled": "^11.11.0", + "npm_package_devDependencies_style_loader": "^0.23.1", + "npm_package_devDependencies__types_react_datepicker": "^3.1.8", + "npm_config_shrinkwrap": "true", + "npm_config_parseable": "", + "npm_config_metrics_registry": "https://registry.npmjs.org/", + "npm_package_dependencies_xregexp": "^4.4.1", + "npm_package_dependencies_shelljs": "^0.8.5", + "npm_package_dependencies_bezier_curve": "^1.0.0", + "npm_package_dependencies__mui_icons_material": "^5.11.16", + "npm_package_devDependencies_tslint": "^5.20.1", + "npm_package_devDependencies__types_react_transition_group": "^4.4.5", + "npm_package_scripts_tsc": "tsc", + "HOMEBREW_REPOSITORY": "/opt/homebrew", + "TMPDIR": "/var/folders/bv/1xck6d7j7bz14bvhgdsllbj40000gn/T/", + "npm_config_timing": "", + "npm_config_init_license": "ISC", + "npm_package_dependencies_socket_io": "^2.5.0", + "npm_package_dependencies_probe_image_size": "^4.0.0", + "npm_package_dependencies_canvas": "^2.9.3", + "npm_package_dependencies__hig_theme_data": "^2.23.1", + "npm_package_devDependencies__types_react_select": "^3.1.2", + "npm_package_devDependencies__types_prosemirror_model": "^1.16.1", + "npm_config_if_present": "", + "npm_package_dependencies_typescript_collections": "^1.3.3", + "npm_package_dependencies_rimraf": "^3.0.0", + "npm_package_dependencies_react_autosuggest": "^9.4.3", + "npm_package_dependencies_flexlayout_react": "^0.3.11", + "npm_package_dependencies_find_in_files": "^0.5.0", + "npm_package_devDependencies__types_chai": "^4.3.0", + "TERM_PROGRAM_VERSION": "1.79.2", + "npm_package_dependencies_prosemirror_inputrules": "^1.1.3", + "npm_package_dependencies_bcrypt_nodejs": "0.0.3", + "npm_package_dependencies_async": "^2.6.2", + "npm_config_sign_git_tag": "", + "npm_config_init_author_email": "", + "npm_config_cache_max": "Infinity", + "npm_package_dependencies_uuid": "^3.4.0", + "npm_package_dependencies_supercluster": "^7.1.4", + "npm_package_dependencies_remark_gfm": "^3.0.1", + "npm_package_dependencies_connect_mongo": "^2.0.3", + "npm_package_dependencies_browser_assert": "^1.2.1", + "npm_package_devDependencies_sass_loader": "^7.3.1", + "ZDOTDIR": "/Users/smallwhale", + "ORIGINAL_XDG_CURRENT_DESKTOP": "undefined", + "MallocNanoZone": "0", + "npm_config_preid": "", + "npm_config_long": "", + "npm_config_local_address": "", + "npm_config_git_tag_version": "true", + "npm_config_cert": "", + "npm_package_dependencies_js_datepicker": "^4.6.6", + "npm_package_devDependencies__types_webpack_hot_middleware": "^2.25.6", + "npm_package_devDependencies__types_mongodb": "^3.6.20", + "npm_package_devDependencies__types_mocha": "^5.2.6", + "TERM_SESSION_ID": "F2049B14-CDC7-4078-A2C4-82A8C869F43E", + "npm_config_registry": "https://registry.npmjs.org/", + "npm_config_noproxy": "", + "npm_config_fetch_retries": "2", + "npm_package_dependencies_react_compound_slider": "^2.5.0", + "npm_package_dependencies_prosemirror_history": "^1.2.0", + "npm_package_devDependencies__types_react_color": "^2.17.6", + "npm_package_devDependencies__types_google_maps_react": "^2.0.5", + "npm_package_devDependencies__types_color": "^3.0.3", + "npm_package_dependencies_react_dom": "^18.2.0", + "npm_package_dependencies_passport_local": "^1.0.0", + "npm_package_dependencies__octokit_core": "^4.0.4", + "npm_package_devDependencies__types_async": "^2.4.1", + "npm_package_scripts_debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts", + "npm_package_scripts_oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts", + "npm_config_versions": "", + "npm_config_message": "%s", + "npm_config_key": "", + "npm_package_readmeFilename": "README.md", + "npm_package_dependencies_react_refresh_typescript": "^2.0.7", + "npm_package_dependencies_image_size": "^0.7.5", + "npm_package_dependencies_html_to_text": "^5.1.1", + "npm_package_dependencies_express_validator": "^5.3.1", + "npm_package_devDependencies_eslint_plugin_jsx_a11y": "^6.6.0", + "npm_package_node_child_process": "empty", + "npm_package_dependencies_react_resizable_rotatable_draggable": "^0.2.0", + "npm_package_dependencies_got": "^12.0.1", + "npm_package_dependencies__types_d3_color": "^2.0.3", + "npm_package_devDependencies_webpack": "^5.69.1", + "npm_package_devDependencies__types_nodemailer": "^4.6.6", + "npm_package_description": "Install Node.js, then, from the project directory, run", + "NVM_DIR": "/Users/smallwhale/.nvm", + "USER": "smallwhale", + "npm_package_dependencies__types_d3_scale": "^3.3.2", + "npm_package_devDependencies_dotenv": "^8.6.0", + "npm_package_devDependencies__types_react": "^18.0.15", + "npm_package_devDependencies__types_prosemirror_transform": "^1.1.5", + "npm_package_devDependencies__types_prosemirror_history": "^1.0.3", + "npm_package_dependencies_readline": "^1.3.0", + "npm_package_dependencies__types_supercluster": "^7.1.0", + "COMMAND_MODE": "unix2003", + "npm_config_globalconfig": "/Users/smallwhale/.nvm/versions/node/v12.16.0/etc/npmrc", + "npm_package_dependencies_depcheck": "^0.9.2", + "npm_package_dependencies__types_web": "0.0.53", + "npm_config_prefer_online": "", + "npm_config_logs_max": "10", + "npm_config_always_auth": "", + "npm_package_dependencies_react_icons": "^4.3.1", + "npm_package_dependencies_passport_google_oauth20": "^2.0.0", + "npm_package_devDependencies_webpack_dev_server": "^3.11.3", + "npm_package_devDependencies__types_brotli": "^1.3.1", + "npm_package_dependencies_url_loader": "^1.1.2", + "npm_package_dependencies_stream_browserify": "^3.0.0", + "npm_package_dependencies_prosemirror_transform": "^1.3.4", + "npm_package_dependencies_lodash": "^4.17.21", + "npm_package_dependencies_i": "^0.3.7", + "npm_package_devDependencies_tslint_loader": "^3.6.0", + "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.iMTrb4REX6/Listeners", + "npm_package_dependencies_words_to_numbers": "^1.5.1", + "npm_package_dependencies_valid_url": "^1.0.9", + "npm_package_dependencies_styled_components": "^4.4.1", + "npm_package_dependencies_csv_parser": "^3.0.0", + "npm_package_dependencies_class_transformer": "^0.2.0", + "npm_package_devDependencies_eslint": "^8.36.0", + "npm_package_devDependencies__types_prosemirror_inputrules": "^1.0.4", + "npm_package_devDependencies__types_express": "^4.17.13", + "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0", + "npm_execpath": "/Users/smallwhale/.nvm/versions/node/v12.16.0/lib/node_modules/npm/bin/npm-cli.js", + "npm_config_global_style": "", + "npm_config_cache_lock_retries": "10", + "npm_package_dependencies_wikijs": "^6.3.3", + "npm_package_dependencies_bluebird": "^3.7.2", + "npm_package_devDependencies__types_react_typist": "^2.0.3", + "npm_config_update_notifier": "true", + "npm_config_cafile": "", + "npm_package_dependencies_util": "^0.12.4", + "npm_package_dependencies_raw_loader": "^1.0.0", + "npm_package_dependencies_https_browserify": "^1.0.0", + "npm_package_dependencies_brotli": "^1.3.3", + "npm_package_dependencies__mui_material": "^5.13.1", + "npm_package_dependencies__fortawesome_react_fontawesome": "^0.2.0", + "npm_package_devDependencies__types_passport_google_oauth20": "^2.0.11", + "npm_package_dependencies_cors": "^2.8.5", + "npm_package_dependencies_bezier_js": "^4.1.1", + "npm_package_dependencies__fortawesome_free_brands_svg_icons": "^6.3.0", + "npm_config_heading": "npm", + "npm_config_audit_level": "low", + "npm_package_dependencies_chrome": "^0.1.0", + "npm_package_dependencies__react_three_fiber": "^6.2.3", + "npm_package_devDependencies_eslint_plugin_prettier": "^4.2.1", + "npm_package_devDependencies_copy_webpack_plugin": "^4.6.0", + "npm_package_devDependencies__types_react_measure": "^2.0.8", + "npm_package_devDependencies__types_react_dom": "^18.0.6", + "npm_package_devDependencies__types_mobile_detect": "^1.3.4", + "npm_config_searchlimit": "20", + "npm_config_read_only": "", + "npm_config_offline": "", + "npm_config_fetch_retry_mintimeout": "10000", + "npm_package_dependencies_react_typist": "^2.0.5", + "npm_package_dependencies_mobx_react_devtools": "^6.1.1", + "npm_package_dependencies_md5_file": "^5.0.0", + "npm_package_dependencies_forever_agent": "^0.6.1", + "npm_package_devDependencies__types_xregexp": "^4.4.0", + "npm_package_devDependencies__types_typescript": "^2.0.0", + "npm_package_devDependencies__types_request": "^2.48.8", + "npm_package_devDependencies__types_prosemirror_commands": "^1.0.4", + "npm_config_json": "", + "npm_config_access": "", + "npm_config_argv": "{\"remain\":[],\"cooked\":[\"start\"],\"original\":[\"start\"]}", + "npm_package_dependencies__fortawesome_free_solid_svg_icons": "^6.3.0", + "npm_package_devDependencies__types_socket_io": "^2.1.13", + "PATH": "/Users/smallwhale/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/.bin:/Users/smallwhale/.nvm/versions/node/v12.16.0/bin:/Users/smallwhale/.nvm/versions/node/v18.16.0/bin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Users/smallwhale/.nvm/versions/node/v18.16.0/bin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/opt/homebrew/bin:/opt/homebrew/sbin", + "npm_config_allow_same_version": "", + "npm_package_dependencies_webrtc_adapter": "^7.7.1", + "npm_package_dependencies_react_reveal": "^1.2.2", + "npm_package_dependencies_prosemirror_schema_list": "^1.1.6", + "npm_package_dependencies__material_ui_core": "^4.12.3", + "npm_package_devDependencies__types_rimraf": "^2.0.5", + "npm_package_devDependencies__types_connect_flash": "0.0.34", + "npm_config_https_proxy": "", + "npm_config_engine_strict": "", + "npm_config_description": "true", + "npm_package_dependencies_pug": "^2.0.4", + "npm_package_dependencies_prosemirror_keymap": "^1.1.5", + "npm_package_dependencies_pdfjs_dist": "^2.14.305", + "npm_package_dependencies_mobile_detect": "^1.4.5", + "npm_package_dependencies_image_size_stream": "^1.1.0", + "npm_package_dependencies_golden_layout": "^1.5.9", + "npm_package_dependencies_child_process": "^1.0.2", + "npm_package_dependencies__types_d3_axis": "^2.1.3", + "_": "/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/.bin/cross-env", + "LaunchInstanceID": "57D8446D-0E37-4DF2-8110-E0AA2FF019D8", + "npm_config_userconfig": "/Users/smallwhale/.npmrc", + "npm_config_init_module": "/Users/smallwhale/.npm-init.js", + "npm_package_dependencies__react_google_maps_api": "^2.7.0", + "USER_ZDOTDIR": "/Users/smallwhale", + "__CFBundleIdentifier": "com.microsoft.VSCode", + "npm_config_cidr": "", + "npm_package_dependencies_puppeteer": "^3.3.0", + "npm_package_dependencies_prosemirror_view": "^1.26.5", + "npm_package_dependencies_mongodb": "^3.7.3", + "npm_package_dependencies_google_auth_library": "^4.2.4", + "npm_package_dependencies_bootstrap": "^4.6.1", + "npm_package_devDependencies_eslint_config_airbnb": "^19.0.4", + "PWD": "/Users/smallwhale/Desktop/Projects/Dash-Web", + "npm_config_user": "501", + "npm_config_node_version": "12.16.0", + "npm_package_dependencies_node_sass": "^4.14.1", + "npm_package_dependencies_howler": "^2.2.3", + "npm_package_dependencies_expressjs": "^1.0.1", + "npm_package_dependencies_core_js": "^3.28.0", + "npm_package_dependencies_browndash_components": "0.0.82", + "npm_package_devDependencies_eslint_plugin_react_hooks": "^4.6.0", + "npm_package_devDependencies__types_lodash": "^4.14.179", + "npm_lifecycle_event": "start", + "npm_package_dependencies_react_table": "^6.11.5", + "npm_package_dependencies_react_loading": "^2.0.3", + "npm_package_dependencies_mobx": "^5.15.7", + "npm_package_dependencies_babel": "^6.23.0", + "npm_package_devDependencies_jsdom": "^15.2.1", + "npm_package_devDependencies_chai": "^4.3.6", + "npm_config_save": "true", + "npm_config_ignore_prepublish": "", + "npm_config_editor": "vi", + "npm_config_auth_type": "legacy", + "npm_package_dependencies_npm": "^6.14.18", + "npm_package_dependencies_node_stream_zip": "^1.15.0", + "npm_package_dependencies_image_data_uri": "^2.0.1", + "npm_package_scripts_start_release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", + "npm_package_name": "dash", + "LANG": "en_US.UTF-8", + "npm_config_tag": "latest", + "npm_config_script_shell": "", + "npm_package_dependencies_query_string": "^6.14.1", + "npm_package_dependencies_mobx_utils": "^5.6.2", + "npm_package_dependencies_file_saver": "^2.0.5", + "npm_package_dependencies_body_parser": "^1.19.2", + "npm_package_dependencies__types_reveal": "^3.3.33", + "npm_package_devDependencies_eslint_plugin_import": "^2.26.0", + "npm_package_devDependencies__types_prosemirror_view": "^1.23.1", + "npm_config_progress": "true", + "npm_config_global": "", + "npm_config_before": "", + "npm_package_dependencies_xoauth2": "^1.2.0", + "npm_package_dependencies_standard_http_error": "^2.0.1", + "npm_package_dependencies_react_loader_spinner": "^5.3.4", + "npm_package_dependencies_http_browserify": "^1.7.0", + "npm_package_dependencies__types_d3_selection": "^2.0.1", + "npm_package_dependencies__hig_flyout": "^1.3.1", + "npm_package_devDependencies_fork_ts_checker_webpack_plugin": "^1.6.0", + "npm_package_scripts_build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production", + "npm_package_scripts_start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts", + "npm_config_searchstaleness": "900", + "npm_config_optional": "true", + "npm_config_ham_it_up": "", + "npm_package_dependencies_sharp": "^0.23.4", + "npm_package_dependencies_rc_switch": "^1.9.2", + "npm_package_dependencies_googlephotos": "^0.2.5", + "npm_package_dependencies_exifr": "^7.1.3", + "npm_package_dependencies__types_google_maps": "^3.2.3", + "npm_package_dependencies__types_bezier_js": "^4.1.0", + "npm_package_dependencies__ffmpeg_core": "0.10.0", + "npm_package_devDependencies_ts_loader": "^5.3.3", + "npm_package_devDependencies__types_bcrypt_nodejs": "0.0.30", + "VSCODE_GIT_ASKPASS_EXTRA_ARGS": "--ms-enable-electron-run-as-node", + "XPC_FLAGS": "0x0", + "npm_config_save_prod": "", + "npm_config_force": "", + "npm_config_bin_links": "true", + "npm_package_devDependencies__types_youtube": "0.0.39", + "npm_config_searchopts": "", + "npm_package_dependencies_react_beautiful_dnd": "^13.1.0", + "npm_package_dependencies_jszip": "^3.7.1", + "npm_package_dependencies_csv_stringify": "^6.3.0", + "npm_package_devDependencies__types_react_icons": "^3.0.0", + "npm_config_node_gyp": "/Users/smallwhale/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js", + "npm_config_depth": "Infinity", + "npm_package_dependencies_google_maps_react": "^2.0.6", + "npm_package_dependencies_express_session": "^1.17.2", + "npm_package_devDependencies_eslint_plugin_node": "^11.1.0", + "npm_package_devDependencies_eslint_config_prettier": "^8.5.0", + "npm_package_main": "index.js", + "npm_config_sso_poll_frequency": "500", + "npm_config_rebuild_bundle": "true", + "npm_package_dependencies_chart_js": "^3.8.0", + "npm_package_dependencies__emotion_react": "^11.11.0", + "npm_package_devDependencies__types_prosemirror_menu": "^1.0.6", + "npm_package_devDependencies__types_prosemirror_keymap": "^1.0.4", + "npm_package_devDependencies__types_pdfjs_dist": "^2.10.378", + "npm_package_devDependencies__types_exif": "^0.6.3", + "npm_package_version": "1.0.0", + "XPC_SERVICE_NAME": "0", + "npm_config_unicode": "true", + "VSCODE_INJECTION": "1", + "npm_package_dependencies_typescript_language_server": "^0.4.0", + "npm_package_dependencies_prosemirror_model": "^1.18.1", + "npm_package_dependencies__ffmpeg_ffmpeg": "0.10.0", + "SHLVL": "4", + "HOME": "/Users/smallwhale", + "npm_config_fetch_retry_maxtimeout": "60000", + "npm_package_dependencies_request_promise": "^4.2.6", + "npm_package_dependencies_react_markdown": "^8.0.3", + "npm_package_dependencies__hig_theme_context": "^2.1.3", + "npm_package_devDependencies__types_react_autosuggest": "^9.3.14", + "npm_package_devDependencies__types_mongoose": "^5.11.97", + "npm_package_devDependencies__types_d3": "^7.4.0", + "npm_package_devDependencies__types_animejs": "^2.0.2", + "npm_package_scripts_test": "mocha -r ts-node/register test/**/*.ts", + "VSCODE_GIT_ASKPASS_MAIN": "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass-main.js", + "npm_config_tag_version_prefix": "v", + "npm_config_strict_ssl": "true", + "npm_config_sso_type": "oauth", + "npm_config_scripts_prepend_node_path": "warn-only", + "npm_config_save_prefix": "^", + "npm_config_loglevel": "notice", + "npm_config_ca": "", + "npm_package_dependencies_three": "^0.127.0", + "npm_package_dependencies_openai": "^3.2.1", + "npm_package_dependencies_mobx_react": "^5.4.4", + "npm_package_dependencies_cookie_parser": "^1.4.6", + "npm_package_dependencies_adm_zip": "^0.4.16", + "npm_package_devDependencies_eslint_config_node": "^4.1.0", + "npm_config_save_exact": "", + "npm_config_group": "20", + "npm_config_fetch_retry_factor": "10", + "npm_config_dev": "", + "npm_package_devDependencies_webpack_hot_middleware": "^2.25.1", + "npm_package_devDependencies_cross_env": "^5.2.1", + "HOMEBREW_PREFIX": "/opt/homebrew", + "npm_config_version": "", + "npm_config_prefer_offline": "", + "npm_config_cache_lock_stale": "60000", + "npm_package_devDependencies__types_prosemirror_state": "^1.2.8", + "npm_package_devDependencies__types_body_parser": "^1.19.2", + "npm_config_otp": "", + "npm_config_cache_min": "10", + "npm_package_dependencies_react_color": "^2.19.3", + "npm_package_dependencies_d3": "^7.6.1", + "npm_package_devDependencies_ts_node": "^10.9.1", + "npm_package_devDependencies__types_react_grid_layout": "^1.3.2", + "npm_config_searchexclude": "", + "npm_config_cache": "/Users/smallwhale/.npm", + "npm_package_dependencies_tough_cookie": "^4.0.0", + "npm_package_dependencies_googleapis": "^40.0.0", + "npm_package_devDependencies__types_valid_url": "^1.0.3", + "npm_package_devDependencies__types_passport": "^1.0.9", + "npm_package_devDependencies__types_adm_zip": "^0.4.34", + "LOGNAME": "smallwhale", + "npm_lifecycle_script": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts", + "npm_config_color": "true", + "npm_package_dependencies_solr_node": "^1.2.1", + "npm_package_dependencies_react_transition_group": "^4.4.2", + "npm_package_dependencies_iink_js": "^1.5.4", + "npm_package_dependencies_html_webpack_plugin": "^5.5.0", + "npm_config_proxy": "", + "npm_config_package_lock": "true", + "npm_package_dependencies_prosemirror_state": "^1.4.1", + "npm_package_dependencies_nodemon": "^1.19.4", + "npm_package_dependencies_function_plot": "^1.22.8", + "npm_package_dependencies_equation_editor_react": "github:bobzel/equation-editor-react#useLocally", + "npm_package_devDependencies__types_socket_io_parser": "^3.0.0", + "VSCODE_GIT_IPC_HANDLE": "/var/folders/bv/1xck6d7j7bz14bvhgdsllbj40000gn/T/vscode-git-b8c8318074.sock", + "npm_config_package_lock_only": "", + "npm_config_fund": "true", + "npm_package_dependencies_react": "^18.2.0", + "npm_package_dependencies_bingmaps_react": "^1.2.10", + "npm_package_devDependencies_scss_loader": "0.0.1", + "npm_package_devDependencies__types_cookie_session": "^2.0.44", + "npm_config_save_optional": "", + "npm_package_dependencies_textarea_caret": "^3.1.0", + "npm_package_dependencies_react_measure": "^2.5.2", + "npm_package_dependencies_exif": "^0.6.0", + "NVM_BIN": "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin", + "npm_config_ignore_scripts": "", + "npm_config_user_agent": "npm/6.13.4 node/v12.16.0 darwin x64", + "npm_package_dependencies_react_resizable": "^1.11.1", + "npm_package_dependencies_prosemirror_commands": "^1.2.1", + "npm_package_dependencies_memorystream": "^0.3.1", + "npm_package_dependencies_formidable": "1.2.1", + "npm_package_devDependencies__types_uuid": "^3.4.10", + "npm_config_cache_lock_wait": "10000", + "npm_package_dependencies_socket_io_client": "^2.5.0", + "npm_package_dependencies_recharts": "^2.1.12", + "npm_package_dependencies_react_chartjs_2": "^4.3.0", + "npm_package_dependencies_fluent_ffmpeg": "^2.1.2", + "npm_package_dependencies__types_cors": "^2.8.12", + "npm_package_devDependencies__types_node": "^10.17.60", + "npm_package_devDependencies__types_file_saver": "^2.0.5", + "VSCODE_GIT_ASKPASS_NODE": "/Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper (Plugin).app/Contents/MacOS/Code Helper (Plugin)", + "GIT_ASKPASS": "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass.sh", + "HOMEBREW_CELLAR": "/opt/homebrew/Cellar", + "INFOPATH": "/opt/homebrew/share/info:/opt/homebrew/share/info:", + "npm_config_production": "", + "npm_package_dependencies_jsonschema": "^1.4.0", + "npm_package_dependencies_ffmpeg": "0.0.4", + "npm_package_dependencies_cookie_session": "^2.0.0", + "npm_package_dependencies_color": "^3.2.1", + "npm_package_devDependencies__types_webpack": "^4.41.32", + "npm_package_devDependencies__types_request_promise": "^4.1.48", + "npm_package_devDependencies__types_prosemirror_schema_list": "^1.0.3", + "npm_config_send_metrics": "", + "npm_config_save_bundle": "", + "npm_package_dependencies_web_request": "^1.0.7", + "npm_package_dependencies_react_datepicker": "^3.8.0", + "npm_package_dependencies_express": "^4.17.3", + "npm_package_dependencies_D": "^1.0.0", + "npm_package_dependencies__types_formidable": "1.0.31", + "npm_package_devDependencies__types_rc_switch": "^1.9.2", + "npm_package_devDependencies__types_prosemirror_dev_tools": "^2.1.0", + "npm_package_devDependencies__types_jquery": "^3.5.14", + "npm_config_umask": "0022", + "npm_config_node_options": "", + "npm_config_init_version": "1.0.0", + "npm_package_dependencies_https": "^1.0.0", + "npm_package_dependencies_array_batcher": "^1.2.3", + "npm_package_dependencies__fortawesome_free_regular_svg_icons": "^6.3.0", + "npm_package_devDependencies__types_shelljs": "^0.8.11", + "npm_package_devDependencies__types_libxmljs": "^0.18.7", + "npm_package_devDependencies__types_express_validator": "^3.0.0", + "npm_package_devDependencies__types_bluebird": "^3.5.36", + "npm_config_init_author_name": "", + "npm_config_git": "git", + "npm_config_scope": "", + "npm_package_dependencies_react_select": "^3.2.0", + "npm_package_dependencies_pdf_parse": "^1.1.1", + "npm_package_dependencies_colors": "^1.4.0", + "npm_package_dependencies_archiver": "^3.1.1", + "npm_package_devDependencies_css_loader": "^2.1.1", + "npm_package_devDependencies__types_socket_io_client": "^1.4.36", + "SECURITYSESSIONID": "186a4", + "npm_config_unsafe_perm": "true", + "npm_config_tmp": "/var/folders/bv/1xck6d7j7bz14bvhgdsllbj40000gn/T", + "npm_config_onload_script": "", + "npm_package_dependencies_serializr": "^1.5.4", + "npm_package_dependencies_fit_curve": "^0.1.7", + "npm_package_dependencies__webscopeio_react_textarea_autocomplete": "^4.9.1", + "npm_package_dependencies__types_three": "^0.126.2", + "npm_package_devDependencies_ts_node_dev": "^2.0.0", + "npm_node_execpath": "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node", + "npm_config_prefix": "/Users/smallwhale/.nvm/versions/node/v12.16.0", + "npm_config_link": "", + "npm_config_format_package_lock": "true", + "npm_package_dependencies_passport": "^0.4.0", + "npm_package_devDependencies_eslint_plugin_react": "^7.30.1", + "npm_package_devDependencies__types_react_table": "^6.8.9", + "npm_package_devDependencies__types_react_reconciler": "^0.26.4", + "COLORTERM": "truecolor", + "NODE_OPTIONS": "--max_old_space_size=4096", + "TS_NODE_DEV": "true", + "_CLIENT_OPENAI_KEY": "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", + "_CLIENT_GITHUB_ACCESS_TOKEN": "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", + "VIPSHOME": "/usr/local/Cellar/vips/8.8.1", + "TYPESCRIPT_PATH": "/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/typescript/lib/typescript.js", + "TSCONFIG": "/Users/smallwhale/Desktop/Projects/Dash-Web/tsconfig.json", + "COMPILER_OPTIONS": "{}", + "TSLINT": "true", + "CONTEXT": "/Users/smallwhale/Desktop/Projects/Dash-Web", + "TSLINTAUTOFIX": "false", + "ESLINT": "false", + "ESLINT_OPTIONS": "{}", + "WATCH": "", + "WORK_DIVISION": "1", + "MEMORY_LIMIT": "2048", + "CHECK_SYNTACTIC_ERRORS": "false", + "USE_INCREMENTAL_API": "true", + "VUE": "false" + }, + "userLimits": { + "core_file_size_blocks": { + "soft": 0, + "hard": "unlimited" + }, + "data_seg_size_kbytes": { + "soft": "unlimited", + "hard": "unlimited" + }, + "file_size_blocks": { + "soft": "unlimited", + "hard": "unlimited" + }, + "max_locked_memory_bytes": { + "soft": "unlimited", + "hard": "unlimited" + }, + "max_memory_size_kbytes": { + "soft": "unlimited", + "hard": "unlimited" + }, + "open_files": { + "soft": 1048575, + "hard": "unlimited" + }, + "stack_size_bytes": { + "soft": 8372224, + "hard": 67092480 + }, + "cpu_time_seconds": { + "soft": "unlimited", + "hard": "unlimited" + }, + "max_user_processes": { + "soft": 1333, + "hard": 2000 + }, + "virtual_memory_kbytes": { + "soft": "unlimited", + "hard": "unlimited" + } + }, + "sharedObjects": [ + "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node", + "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", + "/usr/lib/libobjc.A.dylib", + "/System/Library/PrivateFrameworks/CoreServicesInternal.framework/Versions/A/CoreServicesInternal", + "/usr/lib/liboah.dylib", + "/usr/lib/libfakelink.dylib", + "/usr/lib/libicucore.A.dylib", + "/usr/lib/libSystem.B.dylib", + "/System/Library/PrivateFrameworks/SoftLinking.framework/Versions/A/SoftLinking", + "/usr/lib/libc++abi.dylib", + "/usr/lib/libc++.1.dylib", + "/usr/lib/system/libcache.dylib", + "/usr/lib/system/libcommonCrypto.dylib", + "/usr/lib/system/libcompiler_rt.dylib", + "/usr/lib/system/libcopyfile.dylib", + "/usr/lib/system/libcorecrypto.dylib", + "/usr/lib/system/libdispatch.dylib", + "/usr/lib/system/libdyld.dylib", + "/usr/lib/system/libkeymgr.dylib", + "/usr/lib/system/libmacho.dylib", + "/usr/lib/system/libquarantine.dylib", + "/usr/lib/system/libremovefile.dylib", + "/usr/lib/system/libsystem_asl.dylib", + "/usr/lib/system/libsystem_blocks.dylib", + "/usr/lib/system/libsystem_c.dylib", + "/usr/lib/system/libsystem_collections.dylib", + "/usr/lib/system/libsystem_configuration.dylib", + "/usr/lib/system/libsystem_containermanager.dylib", + "/usr/lib/system/libsystem_coreservices.dylib", + "/usr/lib/system/libsystem_darwin.dylib", + "/usr/lib/system/libsystem_dnssd.dylib", + "/usr/lib/system/libsystem_featureflags.dylib", + "/usr/lib/system/libsystem_info.dylib", + "/usr/lib/system/libsystem_m.dylib", + "/usr/lib/system/libsystem_malloc.dylib", + "/usr/lib/system/libsystem_networkextension.dylib", + "/usr/lib/system/libsystem_notify.dylib", + "/usr/lib/system/libsystem_sandbox.dylib", + "/usr/lib/system/libsystem_secinit.dylib", + "/usr/lib/system/libsystem_kernel.dylib", + "/usr/lib/system/libsystem_platform.dylib", + "/usr/lib/system/libsystem_pthread.dylib", + "/usr/lib/system/libsystem_symptoms.dylib", + "/usr/lib/system/libsystem_trace.dylib", + "/usr/lib/system/libunwind.dylib", + "/usr/lib/system/libxpc.dylib", + "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices", + "/usr/lib/libDiagnosticMessagesClient.dylib", + "/usr/lib/libenergytrace.dylib", + "/usr/lib/libbsm.0.dylib", + "/usr/lib/libz.1.dylib", + "/usr/lib/system/libkxld.dylib", + "/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices", + "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList", + "/System/Library/Frameworks/Security.framework/Versions/A/Security", + "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration", + "/usr/lib/libapple_nghttp2.dylib", + "/usr/lib/libcompression.dylib", + "/usr/lib/libnetwork.dylib", + "/usr/lib/libsqlite3.dylib", + "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation", + "/System/Library/Frameworks/Network.framework/Versions/A/Network", + "/usr/lib/libCoreEntitlements.dylib", + "/System/Library/PrivateFrameworks/MessageSecurity.framework/Versions/A/MessageSecurity", + "/System/Library/PrivateFrameworks/ProtocolBuffer.framework/Versions/A/ProtocolBuffer", + "/usr/lib/libMobileGestalt.dylib", + "/System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression", + "/usr/lib/libcoretls.dylib", + "/usr/lib/libcoretls_cfhelpers.dylib", + "/usr/lib/libpam.2.dylib", + "/usr/lib/libxar.1.dylib", + "/System/Library/PrivateFrameworks/CoreAutoLayout.framework/Versions/A/CoreAutoLayout", + "/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration", + "/usr/lib/libarchive.2.dylib", + "/usr/lib/libxml2.2.dylib", + "/usr/lib/liblangid.dylib", + "/System/Library/Frameworks/Combine.framework/Versions/A/Combine", + "/usr/lib/swift/libswiftCore.dylib", + "/usr/lib/swift/libswiftCoreFoundation.dylib", + "/usr/lib/swift/libswiftDarwin.dylib", + "/usr/lib/swift/libswiftDispatch.dylib", + "/usr/lib/swift/libswiftIOKit.dylib", + "/usr/lib/swift/libswiftObjectiveC.dylib", + "/usr/lib/swift/libswiftXPC.dylib", + "/usr/lib/swift/libswift_Concurrency.dylib", + "/usr/lib/swift/libswift_StringProcessing.dylib", + "/usr/lib/swift/libswiftos.dylib", + "/System/Library/PrivateFrameworks/AppleSystemInfo.framework/Versions/A/AppleSystemInfo", + "/System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/Versions/A/IOMobileFramebuffer", + "/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface", + "/usr/lib/libpcap.A.dylib", + "/usr/lib/libdns_services.dylib", + "/usr/lib/liblzma.5.dylib", + "/usr/lib/libbz2.1.0.dylib", + "/usr/lib/libiconv.2.dylib", + "/usr/lib/libcharset.1.dylib", + "/usr/lib/swift/libswift_RegexParser.dylib", + "/usr/lib/libheimdal-asn1.dylib", + "/usr/lib/libCheckFix.dylib", + "/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC", + "/System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP", + "/System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate", + "/usr/lib/libmecab.dylib", + "/usr/lib/libCRFSuite.dylib", + "/usr/lib/libgermantok.dylib", + "/usr/lib/libThaiTokenizer.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib", + "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib", + "/System/Library/PrivateFrameworks/MIL.framework/Versions/A/MIL", + "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory", + "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory", + "/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS", + "/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation", + "/usr/lib/libutil.dylib", + "/System/Library/PrivateFrameworks/InstalledContentLibrary.framework/Versions/A/InstalledContentLibrary", + "/System/Library/PrivateFrameworks/CoreServicesStore.framework/Versions/A/CoreServicesStore", + "/usr/lib/libapp_launch_measurement.dylib", + "/System/Library/PrivateFrameworks/AppleMobileFileIntegrity.framework/Versions/A/AppleMobileFileIntegrity", + "/usr/lib/libmis.dylib", + "/System/Library/PrivateFrameworks/MobileSystemServices.framework/Versions/A/MobileSystemServices", + "/System/Library/PrivateFrameworks/ConfigProfileHelper.framework/Versions/A/ConfigProfileHelper", + "/System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics", + "/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce", + "/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling", + "/usr/lib/libxslt.1.dylib", + "/usr/lib/libcmph.dylib", + "/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji", + "/System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData", + "/System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon", + "/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement", + "/usr/lib/libTLE.dylib", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices", + "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics", + "/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText", + "/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO", + "/System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis", + "/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight", + "/System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib", + "/System/Library/PrivateFrameworks/RunningBoardServices.framework/Versions/A/RunningBoardServices", + "/System/Library/PrivateFrameworks/IOSurfaceAccelerator.framework/Versions/A/IOSurfaceAccelerator", + "/System/Library/PrivateFrameworks/WatchdogClient.framework/Versions/A/WatchdogClient", + "/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay", + "/System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia", + "/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator", + "/System/Library/Frameworks/Metal.framework/Versions/A/Metal", + "/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo", + "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders", + "/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport", + "/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore", + "/System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox", + "/System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard", + "/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG", + "/usr/lib/libexpat.1.dylib", + "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib", + "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib", + "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib", + "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib", + "/usr/lib/libate.dylib", + "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib", + "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib", + "/System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler", + "/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment", + "/System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay", + "/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/31001/Libraries/libllvm-flatbuffers.dylib", + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib", + "/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/31001/Libraries/libGPUCompilerUtils.dylib", + "/System/Library/PrivateFrameworks/CMCaptureCore.framework/Versions/A/CMCaptureCore", + "/usr/lib/libspindump.dylib", + "/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio", + "/System/Library/Frameworks/ExtensionFoundation.framework/Versions/A/ExtensionFoundation", + "/System/Library/PrivateFrameworks/CoreTime.framework/Versions/A/CoreTime", + "/System/Library/PrivateFrameworks/AppServerSupport.framework/Versions/A/AppServerSupport", + "/System/Library/PrivateFrameworks/perfdata.framework/Versions/A/perfdata", + "/System/Library/PrivateFrameworks/AudioToolboxCore.framework/Versions/A/AudioToolboxCore", + "/System/Library/PrivateFrameworks/caulk.framework/Versions/A/caulk", + "/usr/lib/libAudioStatistics.dylib", + "/System/Library/PrivateFrameworks/SystemPolicy.framework/Versions/A/SystemPolicy", + "/usr/lib/libSMC.dylib", + "/System/Library/Frameworks/CoreMIDI.framework/Versions/A/CoreMIDI", + "/usr/lib/libAudioToolboxUtility.dylib", + "/System/Library/PrivateFrameworks/OSAServicesClient.framework/Versions/A/OSAServicesClient", + "/usr/lib/libperfcheck.dylib", + "/System/Library/PrivateFrameworks/PlugInKit.framework/Versions/A/PlugInKit", + "/System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices", + "/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL", + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib", + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib", + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib", + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib", + "/usr/lib/libRosetta.dylib", + "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib", + "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSCore.framework/Versions/A/MPSCore", + "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSImage.framework/Versions/A/MPSImage", + "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork", + "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix", + "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector", + "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNDArray.framework/Versions/A/MPSNDArray", + "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSFunctions.framework/Versions/A/MPSFunctions", + "/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools", + "/System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary", + "/usr/lib/libIOReport.dylib", + "/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage", + "/System/Library/PrivateFrameworks/PhotosensitivityProcessing.framework/Versions/A/PhotosensitivityProcessing", + "/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL", + "/System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer", + "/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/FontServices", + "/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers", + "/System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib", + "/System/Library/PrivateFrameworks/FontServices.framework/libhvf.dylib", + "/System/Library/PrivateFrameworks/FontServices.framework/libXTFontStaticRegistryData.dylib", + "/System/Library/PrivateFrameworks/VideoToolboxParavirtualizationSupport.framework/Versions/A/VideoToolboxParavirtualizationSupport", + "/System/Library/PrivateFrameworks/AppleVA.framework/Versions/A/AppleVA", + "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATSUI.framework/Versions/A/ATSUI", + "/usr/lib/libcups.2.dylib", + "/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos", + "/System/Library/Frameworks/GSS.framework/Versions/A/GSS", + "/usr/lib/libresolv.9.dylib", + "/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal", + "/System/Library/Frameworks/Kerberos.framework/Versions/A/Libraries/libHeimdalProxy.dylib", + "/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth", + "/System/Library/Frameworks/AVFAudio.framework/Versions/A/AVFAudio", + "/System/Library/PrivateFrameworks/AXCoreUtilities.framework/Versions/A/AXCoreUtilities", + "/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox", + "/System/Library/PrivateFrameworks/AudioSession.framework/Versions/A/AudioSession", + "/System/Library/Frameworks/IOBluetooth.framework/Versions/A/IOBluetooth", + "/System/Library/PrivateFrameworks/MediaExperience.framework/Versions/A/MediaExperience", + "/System/Library/PrivateFrameworks/AudioSession.framework/libSessionUtility.dylib", + "/System/Library/PrivateFrameworks/AudioResourceArbitration.framework/Versions/A/AudioResourceArbitration", + "/System/Library/PrivateFrameworks/PowerLog.framework/Versions/A/PowerLog", + "/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData", + "/System/Library/Frameworks/CoreBluetooth.framework/Versions/A/CoreBluetooth", + "/System/Library/Frameworks/AudioUnit.framework/Versions/A/AudioUnit", + "/System/Library/PrivateFrameworks/CoreUtils.framework/Versions/A/CoreUtils", + "/System/Library/PrivateFrameworks/CoreUtilsExtras.framework/Versions/A/CoreUtilsExtras", + "/System/Library/PrivateFrameworks/IO80211.framework/Versions/A/IO80211", + "/System/Library/PrivateFrameworks/MobileKeyBag.framework/Versions/A/MobileKeyBag", + "/System/Library/PrivateFrameworks/MallocStackLogging.framework/Versions/A/MallocStackLogging" + ] +} \ No newline at end of file diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 18222b32a..6b4106f56 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -14,7 +14,7 @@ type GPTCallOpts = { }; const callTypeMap: { [type: string]: GPTCallOpts } = { - summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text briefly: ' }, + summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' }, }; @@ -47,7 +47,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { } }; -const gptImageCall = async (prompt: string) => { +const gptImageCall = async (prompt: string, n?: number) => { try { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY, @@ -55,33 +55,15 @@ const gptImageCall = async (prompt: string) => { const openai = new OpenAIApi(configuration); const response = await openai.createImage({ prompt: prompt, - n: 1, + n: n ?? 1, size: '1024x1024', }); - return response.data.data[0].url; + return response.data.data.map(data => data.url); + // return response.data.data[0].url; } catch (err) { console.error(err); return; } }; -// const gptEditCall = async (selectedText: string, fullText: string) => { -// try { -// const configuration = new Configuration({ -// apiKey: process.env.OPENAI_KEY, -// }); -// const openai = new OpenAIApi(configuration); -// const response = await openai.createCompletion({ -// model: 'text-davinci-003', -// max_tokens: 256, -// temperature: 0.1, -// prompt: `Replace the phrase ${selectedText} inside of ${fullText}.`, -// }); -// return response.data.choices[0].text.trim(); -// } catch (err) { -// console.log(err); -// return 'Error connecting with API.'; -// } -// }; - export { gptAPICall, gptImageCall, GPTCallType }; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 85101fcab..ac948c7c7 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -195,7 +195,7 @@ export namespace DragManager { } // drag a document and drop it (or make an embed/copy on drop) - export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, dropEvent?: () => any) { + export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) { const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField()); dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc); @@ -203,7 +203,7 @@ export namespace DragManager { }; const finishDrag = async (e: DragCompleteEvent) => { const docDragData = e.docDragData; - dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails + onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; docDragData.droppedDocuments = ( diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 67b7b39dd..1ff5688f9 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -137,7 +137,11 @@ export class TabDocView extends React.Component { setupMoveUpEvents( this, e, - e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY), + e => + !e.defaultPrevented && + DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => { + CollectionDockingView.CloseSplit(doc); + }), returnFalse, action(e => { if (this.view) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index db000d5de..747ab9249 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -238,11 +238,11 @@ export class CollectionFreeFormLinkView extends React.Component {} }; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 34a1229ba..5cdd6b5f2 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -354,6 +354,11 @@ export class WebBox extends ViewBoxAnnotatableComponent, dataDoc: Doc) => void; @@ -896,12 +897,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - console.log(this.dataDoc.text); - if (resIndex < this.gptRes.length) { - this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex]; + animateRes = (resIndex: number, newText: string) => { + if (resIndex < newText.length) { + const marks = this._editorView?.state.storedMarks ?? []; + // if (!marks) return; + this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks)); setTimeout(() => { - this.animateRes(resIndex + 1); + this.animateRes(resIndex + 1, newText); }, 20); } }; @@ -912,47 +914,68 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + // const state = this._editorView?.state; + // if (!state) return; + // const to = state.selection.to; + // const updated = TextSelection.create(state.doc, to, to); + // this._editorView?.dispatch(state.tr.setSelection(updated).insertText('\n', to)); + // this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText('\nTesting').setStoredMarks(marks)); + // console.log('After ', this._editorView?.state.storedMarks); try { let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); - if (res) { - this.gptRes = res; - this.animateRes(0); + if (!res) { + console.error('GPT call failed'); + this.animateRes(0, 'Something went wrong.'); + } else { + this.animateRes(0, res); } } catch (err) { - console.log(err); - this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong'; + console.error('GPT call failed'); + this.animateRes(0, 'Something went wrong.'); } }); generateImage = async () => { console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); + GPTPopup.Instance?.setImgTargetDoc(this.rootDoc); + GPTPopup.Instance.setImgUrls([]); + GPTPopup.Instance.setMode(GPTPopupMode.IMAGE); + GPTPopup.Instance.setVisible(true); + GPTPopup.Instance.addToCollection = this.props.addDocument; + GPTPopup.Instance.setLoading(true); + try { - let image_url = await gptImageCall((this.dataDoc.text as RichTextField)?.Text); - if (image_url) { - const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] }); + // make this support multiple images + let image_urls = await gptImageCall((this.dataDoc.text as RichTextField)?.Text); + console.log(image_urls); + if (image_urls) { + const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] }); const source = Utils.prepend(result.accessPaths.agnostic.client); - const newDoc = Docs.Create.ImageDocument(source, { - x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, - y: NumCast(this.rootDoc.y), - _height: 200, - _width: 200, - data_nativeWidth: result.nativeWidth, - data_nativeHeight: result.nativeHeight, - }); - if (Doc.IsInMyOverlay(this.rootDoc)) { - newDoc.overlayX = this.rootDoc.x; - newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); - Doc.AddToMyOverlay(newDoc); - } else { - this.props.addDocument?.(newDoc); - } - // Create link between prompt and image - DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' }); + GPTPopup.Instance.setImgUrls([source]); + + // const newDoc = Docs.Create.ImageDocument(source, { + // x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, + // y: NumCast(this.rootDoc.y), + // _height: 200, + // _width: 200, + // data_nativeWidth: result.nativeWidth, + // data_nativeHeight: result.nativeHeight, + // }); + // if (Doc.IsInMyOverlay(this.rootDoc)) { + // newDoc.overlayX = this.rootDoc.x; + // newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); + // Doc.AddToMyOverlay(newDoc); + // } else { + // this.props.addDocument?.(newDoc); + // } + // // Create link between prompt and image + // DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' }); } } catch (err) { console.log(err); return ''; } + GPTPopup.Instance.setLoading(false); }; breakupDictation = () => { @@ -1249,8 +1272,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { gptSummarize = async (e: React.PointerEvent) => { GPTPopup.Instance.setVisible(true); this.setHighlightRange(undefined); - this.setGPTMode(GPTPopupMode.SUMMARY); + GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY); GPTPopup.Instance.setLoading(true); try { @@ -183,6 +183,10 @@ export class AnchorMenu extends AntimodeMenu { GPTPopup.Instance.setLoading(false); }; + gptImage = async () => { + console.log(this.GetAnchor(undefined, false)); + }; + /** * Replaces text suggestions from GPT. */ @@ -293,21 +297,12 @@ export class AnchorMenu extends AntimodeMenu { )} - {/* */} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( } color={StrCast(Doc.UserDoc().userColor)} /> )} - {this.canEdit() && } color={StrCast(Doc.UserDoc().userColor)} />} + {/* Removed text editing for now, not quite ready */} + {/* {this.canEdit() && } color={StrCast(Doc.UserDoc().userColor)} />} */} + {} color={StrCast(Doc.UserDoc().userColor)} />} } popup={} color={StrCast(Doc.UserDoc().userColor)} /> {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : ( } color={StrCast(Doc.UserDoc().userColor)} /> diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 2f0ff83e2..478b7d4ba 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -24,6 +24,7 @@ $highlightedText: #82e0ff; .summary-heading { display: flex; + justify-content: space-between; align-items: center; border-bottom: 1px solid $greyborder; padding-bottom: 5px; @@ -110,6 +111,57 @@ $highlightedText: #82e0ff; } } +.image-content-wrapper { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + padding-bottom: 16px; + + .img-wrapper { + position: relative; + cursor: pointer; + + .img-container { + position: relative; + + img { + position: relative; + } + } + + .img-container::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + opacity: 0; + transition: opacity 0.3s ease; + } + + .btn-container { + position: absolute; + right: 8px; + bottom: 8px; + opacity: 0; + transition: opacity 0.3s ease; + } + + &:hover { + .img-container::after { + opacity: 1; + } + + .btn-container { + opacity: 1; + } + } + } +} + // Typist CSS .Typist .Cursor { display: inline-block; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 9f28cb5d1..aeee90d16 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -1,4 +1,5 @@ import React = require('react'); +import './GPTPopup.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -6,13 +7,14 @@ import ReactLoading from 'react-loading'; import Typist from 'react-typist'; import { Doc } from '../../../../fields/Doc'; import { DocUtils, Docs } from '../../../documents/Documents'; -import './GPTPopup.scss'; -import { Button, IconButton } from 'browndash-components'; -import { StrCast } from '../../../../fields/Types'; +import { Button, IconButton, Type } from 'browndash-components'; +import { NumCast, StrCast } from '../../../../fields/Types'; +import { CgClose } from 'react-icons/cg'; export enum GPTPopupMode { SUMMARY, EDIT, + IMAGE, } interface GPTPopupProps {} @@ -39,6 +41,14 @@ export class GPTPopup extends React.Component { public setText = (text: string) => { this.text = text; }; + + @observable + public imageUrls: string[] = []; + @action + public setImgUrls = (imgs: string[]) => { + this.imageUrls = imgs; + }; + @observable public mode: GPTPopupMode = GPTPopupMode.SUMMARY; @action @@ -58,6 +68,8 @@ export class GPTPopup extends React.Component { public setDone = (done: boolean) => { this.done = done; }; + + // change what can be a ref into a ref @observable private sidebarId: string = ''; @action @@ -65,19 +77,30 @@ export class GPTPopup extends React.Component { this.sidebarId = id; }; - // reference - // if (focusNode) { - // const anchor = srcWeb?.ComponentView?.getAnchor?.(true); - // anchor && DocUtils.MakeLink(htmlDoc, anchor, {}); - // } + // pdfs and webpages + @observable + private targetAnchor: Doc | undefined; + @action + public setTargetAnchor = (anchor: Doc) => { + this.targetAnchor = anchor; + }; + @observable - private pdfAnchor: Doc | undefined; + private imgTargetDoc: Doc | undefined; @action - public setPdfAnchor = (anchor: Doc) => { - this.pdfAnchor = anchor; + public setImgTargetDoc = (anchor: Doc) => { + this.imgTargetDoc = anchor; + }; + + @observable + private textAnchor: Doc | undefined; + @action + public setTextAnchor = (anchor: Doc) => { + this.textAnchor = anchor; }; public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false; + public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; /** * Transfers the summarization text to a sidebar annotation text document. @@ -90,13 +113,42 @@ export class GPTPopup extends React.Component { _layout_autoHeight: true, }); this.addDoc(newDoc, this.sidebarId); - if (this.pdfAnchor) { - DocUtils.MakeLink(newDoc, this.pdfAnchor, { + if (this.targetAnchor) { + DocUtils.MakeLink(newDoc, this.targetAnchor, { link_relationship: 'GPT Summary', }); } }; + /** + * Transfers the image urls to actual image docs + */ + private transferToImage = (source: string) => { + console.log('Text Anchor', this.textAnchor); + console.log('Whole doc anchor', this.imgTargetDoc); + const textAnchor = this.textAnchor ?? this.imgTargetDoc; + if (!textAnchor) return; + const newDoc = Docs.Create.ImageDocument(source, { + x: NumCast(textAnchor.x) + NumCast(textAnchor._width) + 10, + y: NumCast(textAnchor.y), + _height: 200, + _width: 200, + data_nativeWidth: 1024, + data_nativeHeight: 1024, + }); + if (Doc.IsInMyOverlay(textAnchor)) { + newDoc.overlayX = textAnchor.x; + newDoc.overlayY = NumCast(textAnchor.y) + NumCast(textAnchor._height); + Doc.AddToMyOverlay(newDoc); + } else { + this.addToCollection?.(newDoc); + } + // Create link between prompt and image + DocUtils.MakeLink(textAnchor, newDoc, { link_relationship: 'Image Prompt' }); + }; + + private getPreviewUrl = (source: string) => source.split('.').join('_m.'); + constructor(props: GPTPopupProps) { super(props); GPTPopup.Instance = this; @@ -108,6 +160,26 @@ export class GPTPopup extends React.Component { } }; + imageBox = () => { + return ( +
+ {this.heading('GENERATED IMAGE')} +
+ {this.imageUrls.map(rawSrc => ( +
+
+ dalle generation +
+
+
+
+ ))} +
+
+ ); + }; + summaryBox = () => ( <>
@@ -135,30 +207,21 @@ export class GPTPopup extends React.Component {
{this.done ? ( <> - {/* - */} - } color={StrCast(Doc.UserDoc().userVariantColor)} /> - + }} + color={StrCast(Doc.UserDoc().userVariantColor)} + type={Type.TERT} + />
)}
@@ -216,14 +279,14 @@ export class GPTPopup extends React.Component { heading = (headingText: string) => (
- {this.loading && } + {this.loading ? : } onClick={() => this.setVisible(false)} />}
); render() { return (
- {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()} + {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.editBox()}
); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 4fc31ffe3..1d1c34f4f 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -425,8 +425,7 @@ export class PDFViewer extends React.Component { GPTPopup.Instance.setSidebarId('data_sidebar'); const anchor = this._getAnchor(undefined, false); if (anchor) { - console.log(anchor); - GPTPopup.Instance.setPdfAnchor(anchor); + GPTPopup.Instance.setTargetAnchor(anchor); } GPTPopup.Instance.addDoc = this.props.sidebarAddDoc; }; -- cgit v1.2.3-70-g09d2 From 314f62bb5335e3fb3f25501823d41cf0cdc53cac Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Sun, 13 Aug 2023 17:02:58 -0400 Subject: update --- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 +++- .../views/nodes/formattedText/FormattedTextBox.tsx | 15 +-- .../views/nodes/generativeFill/GenerativeFill.scss | 1 + .../views/nodes/generativeFill/GenerativeFill.tsx | 103 +++++++++--------- .../generativeFillUtils/BrushHandler.ts | 116 +++++++-------------- .../generativeFillUtils/ImageHandler.ts | 2 +- .../generativeFillUtils/generativeFillConstants.ts | 4 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 4 +- 8 files changed, 118 insertions(+), 145 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ba31916a7..34f707b30 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -237,7 +237,7 @@ export class CollectionFreeFormView extends CollectionSubView this.props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; - shrinkWrap = () => { + fitContentOnce = () => { if (this.props.DocumentView?.().nativeWidth) return; const vals = this.fitToContentVals; this.layoutDoc._freeform_panX = vals.bounds.cx; @@ -1592,6 +1592,14 @@ export class CollectionFreeFormView extends CollectionSubView (this._layoutElements = elements || []), { fireImmediately: true, name: 'doLayout' } ); + + this._disposers.fitContent = reaction( + () => this.rootDoc.fitContentOnce, + fitContentOnce => { + if (fitContentOnce) this.fitContentOnce(); + }, + { fireImmediately: true, name: 'fitContent' } + ); }) ); } @@ -1782,6 +1790,14 @@ export class CollectionFreeFormView extends CollectionSubView (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' }); this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' }); + this.props.renderDepth && + optionItems.push({ + description: 'Fit Content Once', + event: () => { + this.fitContentOnce(); + }, + icon: 'object-group', + }); if (!Doc.noviceMode) { optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' }); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9f4483e8d..072977150 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -937,6 +937,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); + GPTPopup.Instance?.setTextAnchor(this.getAnchor(false)); GPTPopup.Instance?.setImgTargetDoc(this.rootDoc); GPTPopup.Instance.setImgUrls([]); GPTPopup.Instance.setMode(GPTPopupMode.IMAGE); @@ -1270,13 +1271,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent([0, 0]); // which urls were already saved to canvas - const savedSrcs = useRef([]); + const savedSrcs = useRef>(new Set()); // references to keep track of tree structure const newCollectionRef = useRef(null); const parentDoc = useRef(null); const childrenDocs = useRef([]); + const addToExistingCollection = useRef(false); // Undo and Redo const handleUndo = () => { @@ -250,18 +248,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD })); }; - // File upload - const uploadImg = (e: React.ChangeEvent) => { - if (e.target.files) { - const file = e.target.files[0]; - const image = new Image(); - const imgUrl = URL.createObjectURL(file); - image.src = imgUrl; - ImageUtility.drawImgToCanvas(image, canvasRef); - currImg.current = image; - } - }; - // Get AI Edit const getEdit = async () => { const img = currImg.current; @@ -280,6 +266,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // create first image if (!newCollectionRef.current) { + if (addToExistingCollection.current) { + } if (!(originalImg.current && imageRootDoc)) return; console.log('creating first image'); // create new collection and add it to the view @@ -292,7 +280,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }); DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); // add the doc to the main freeform - addDoc?.(newCollectionRef.current); + // addDoc?.(newCollectionRef.current); await createNewImgDoc(originalImg.current, true); } else { parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; @@ -321,7 +309,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD if (!parentDoc.current) return; const startY = NumCast(parentDoc.current.y); const len = childrenDocs.current.length; - console.log(len); let initialYPositions: number[] = []; for (let i = 0; i < len; i++) { initialYPositions.push(startY + i * offsetDistanceY); @@ -398,9 +385,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }; const handleViewClose = () => { - // if (newCollectionRef.current) { - // CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); - // } + if (newCollectionRef.current) { + newCollectionRef.current.fitContentOnce = true; + CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + } MainView.Instance.setImageEditorOpen(false); MainView.Instance.setImageEditorSource(''); setEdits([]); @@ -409,12 +397,17 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD return (
-

Generative Fill

+

AI Image Editor

+ {saveLoading && ( + + Saving image... + + )}
{/* Main canvas for editing */} @@ -439,20 +432,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
{/* Icons */}
- - { - if (fileRef.current) { - fileRef.current.click(); - } - }}> - - { setBrushStyle(BrushStyle.ADD); }}> - + {/* Undo and Redo */} + + { + setCursorData(prev => ({ ...prev, width: val as number })); + }} + /> +
{/* Edits thumbnails*/}
@@ -484,12 +490,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD width={100} height={100} src={edit} - onClick={() => { + onClick={async () => { + // if (savedSrcs.current.has(edit)) return; const img = new Image(); img.src = edit; ImageUtility.drawImgToCanvas(img, canvasRef); currImg.current = img; - onSave(); + savedSrcs.current.add(edit); + await onSave(); }} /> ))} @@ -521,14 +529,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD currImg.current = img; }} /> -
- {saveLoading && } -
)}
@@ -541,15 +541,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD type="text" label="Prompt" placeholder="Prompt..." - InputLabelProps={{ style: { fontSize: '1.5rem' } }} - inputProps={{ style: { fontSize: '1.5rem' } }} + InputLabelProps={{ style: { fontSize: '16px' } }} + inputProps={{ style: { fontSize: '16px' } }} sx={{ backgroundColor: '#ffffff', position: 'absolute', - bottom: '1rem', + bottom: '16px', transform: 'translateX(calc(50vw - 50%))', - width: 'calc(100vw - 4rem)', - scale: 1.2, + width: 'calc(100vw - 64px)', }} />
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts index c2716e083..f84f04190 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts @@ -1,87 +1,45 @@ -import { GenerativeFillMathHelpers } from "./GenerativeFillMathHelpers"; -import { eraserColor } from "./generativeFillConstants"; -import { Point } from "./generativeFillInterfaces"; +import { GenerativeFillMathHelpers } from './GenerativeFillMathHelpers'; +import { eraserColor } from './generativeFillConstants'; +import { Point } from './generativeFillInterfaces'; export class BrushHandler { - static brushCircle = ( - x: number, - y: number, - brushRadius: number, - ctx: CanvasRenderingContext2D - ) => { - ctx.globalCompositeOperation = "destination-out"; - ctx.shadowColor = "#ffffffeb"; - ctx.shadowBlur = 5; - ctx.beginPath(); - ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); - ctx.fill(); - ctx.closePath(); - }; + static brushCircle = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D) => { + ctx.globalCompositeOperation = 'destination-out'; + ctx.shadowColor = '#ffffffeb'; + ctx.shadowBlur = 5; + ctx.beginPath(); + ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); + ctx.fill(); + ctx.closePath(); + }; - static brushCircleOverlay = ( - x: number, - y: number, - brushRadius: number, - ctx: CanvasRenderingContext2D, - fillColor: string, - erase: boolean - ) => { - ctx.globalCompositeOperation = "destination-out"; - // ctx.globalCompositeOperation = erase ? "destination-out" : "source-over"; - ctx.fillStyle = fillColor; - ctx.shadowColor = eraserColor; - ctx.shadowBlur = 5; - ctx.beginPath(); - ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); - ctx.fill(); - ctx.closePath(); - }; + static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => { + ctx.globalCompositeOperation = 'destination-out'; + // ctx.globalCompositeOperation = erase ? "destination-out" : "source-over"; + ctx.fillStyle = fillColor; + ctx.shadowColor = eraserColor; + ctx.shadowBlur = 5; + ctx.beginPath(); + ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); + ctx.fill(); + ctx.closePath(); + }; - static createBrushPath = ( - startPoint: Point, - endPoint: Point, - brushRadius: number, - ctx: CanvasRenderingContext2D - ) => { - const dist = GenerativeFillMathHelpers.distanceBetween( - startPoint, - endPoint - ); + static createBrushPath = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D) => { + const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint); - for (let i = 0; i < dist; i += 5) { - const s = i / dist; - BrushHandler.brushCircle( - startPoint.x * (1 - s) + endPoint.x * s, - startPoint.y * (1 - s) + endPoint.y * s, - brushRadius, - ctx - ); - } - }; + for (let i = 0; i < dist; i += 5) { + const s = i / dist; + BrushHandler.brushCircle(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx); + } + }; - static createBrushPathOverlay = ( - startPoint: Point, - endPoint: Point, - brushRadius: number, - ctx: CanvasRenderingContext2D, - fillColor: string, - erase: boolean - ) => { - const dist = GenerativeFillMathHelpers.distanceBetween( - startPoint, - endPoint - ); + static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => { + const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint); - for (let i = 0; i < dist; i += 5) { - const s = i / dist; - BrushHandler.brushCircleOverlay( - startPoint.x * (1 - s) + endPoint.x * s, - startPoint.y * (1 - s) + endPoint.y * s, - brushRadius, - ctx, - fillColor, - erase - ); - } - }; + for (let i = 0; i < dist; i += 5) { + const s = i / dist; + BrushHandler.brushCircleOverlay(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx, fillColor, erase); + } + }; } diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index 48055903c..45cf7196b 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -28,7 +28,7 @@ export class ImageUtility { fd.append('image', imgBlob, 'image.png'); fd.append('mask', maskBlob, 'mask.png'); fd.append('prompt', prompt); - fd.append('size', '1024x1024'); + fd.append('size', '512x512'); fd.append('n', n ? JSON.stringify(n) : '1'); fd.append('response_format', 'b64_json'); diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts index 5a8d33742..412a4d238 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts @@ -1,9 +1,9 @@ // constants -export const canvasSize = 1024; +export const canvasSize = 512; export const freeformRenderSize = 300; export const offsetDistanceY = freeformRenderSize + 200; export const offsetX = 200; -export const newCollectionSize = 1000; +export const newCollectionSize = 500; export const activeColor = '#1976d2'; export const eraserColor = '#e1e9ec'; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index aeee90d16..fc6fc1af8 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -124,9 +124,7 @@ export class GPTPopup extends React.Component { * Transfers the image urls to actual image docs */ private transferToImage = (source: string) => { - console.log('Text Anchor', this.textAnchor); - console.log('Whole doc anchor', this.imgTargetDoc); - const textAnchor = this.textAnchor ?? this.imgTargetDoc; + const textAnchor = this.imgTargetDoc; if (!textAnchor) return; const newDoc = Docs.Create.ImageDocument(source, { x: NumCast(textAnchor.x) + NumCast(textAnchor._width) + 10, -- cgit v1.2.3-70-g09d2 From d3ecace5f03233e5d5ab2354c3f482418287ca9a Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 17 Aug 2023 16:14:53 -0400 Subject: broken version --- package-lock.json | 97 +++++++++++++++-- src/client/util/CurrentUserUtils.ts | 2 + src/client/views/MainView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/global/globalScripts.ts | 8 +- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 28 +++++ src/client/views/nodes/ImageBox.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 76 +++++++------- .../views/nodes/generativeFill/GenerativeFill.tsx | 115 +++++++++++++-------- .../nodes/generativeFill/GenerativeFillButtons.tsx | 6 +- src/client/views/pdf/AnchorMenu.tsx | 1 + src/client/views/pdf/GPTPopup/GPTPopup.scss | 2 + src/client/views/pdf/GPTPopup/GPTPopup.tsx | 99 +++++++++--------- src/client/views/pdf/PDFViewer.tsx | 4 - 14 files changed, 301 insertions(+), 146 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index 658e783f9..20d582e5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6507,9 +6507,9 @@ } }, "browndash-components": { - "version": "0.0.92", - "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.92.tgz", - "integrity": "sha512-eE/6WQNZiLnaXUKyoaMm0PDYjExUsFJ9VTAIIxROpYPosIBKWNZ743xaOfmehib5us9hEXJb0CvUFJQb8rzDVw==", + "version": "0.0.93", + "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.93.tgz", + "integrity": "sha512-JL9Hi7Nq+zHZtjLCQSuGhID0Hd7X6sARutyJqbECfsLWACsIC/JfeEQZdUeH5CqO+R7pJKeoWM82AIkWGtN2Xw==", "requires": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -7063,12 +7063,97 @@ "strip-ansi": "^7.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "strip-ansi": { "version": "7.1.0", "bundled": true, "requires": { "ansi-regex": "^6.0.1" } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } } } }, @@ -8573,7 +8658,7 @@ } }, "string-width-cjs": { - "version": "npm:string-width@4.2.3", + "version": "npm:string-width-cjs@4.2.3", "bundled": true, "requires": { "emoji-regex": "^8.0.0", @@ -8596,7 +8681,7 @@ } }, "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", + "version": "npm:strip-ansi-cjs@6.0.1", "bundled": true, "requires": { "ansi-regex": "^5.0.1" @@ -8755,7 +8840,7 @@ } }, "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", + "version": "npm:wrap-ansi-cjs@7.0.0", "bundled": true, "requires": { "ansi-styles": "^4.0.0", diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c9a5175bb..6592ecffc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -633,6 +633,8 @@ export class CurrentUserUtils { { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true, + { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit all Docs to View (persistent)", btnType: ButtonType.IconClickButton, ignoreClick: false, expertMode: false, toolType:"viewAllPersist", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 60eb64caa..8fba9b886 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -80,7 +80,6 @@ export class MainView extends React.Component { @observable public imageEditorSource: string = ''; @action public setImageEditorSource = (source: string) => (this.imageEditorSource = source); @observable public imageRootDoc: Doc | undefined; - @action public setImageRootDoc = (doc: Doc) => (this.imageRootDoc = doc); @observable public addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined; @observable public LastButton: Opt; @@ -350,6 +349,7 @@ export class MainView extends React.Component { fa.faMousePointer, fa.faMusic, fa.faObjectGroup, + fa.faArrowsLeftRight, fa.faPause, fa.faPen, fa.faPenNib, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 29122cb91..f353dfab7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1599,6 +1599,7 @@ export class CollectionFreeFormView extends CollectionSubView this.rootDoc.fitContentOnce, fitContentOnce => { if (fitContentOnce) this.fitContentOnce(); + this.rootDoc.fitContentOnce = false; }, { fireImmediately: true, name: 'fitContent' } ); diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 256377758..38bf1042d 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -85,10 +85,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); -ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) { +ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); // prettier-ignore - const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ + const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc._freeform_backgroundGrid, false), setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, @@ -101,6 +101,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false), setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox, }], + ['viewAllPersist', { + checkResult: (doc:Doc) => false, + setDoc: (doc:Doc) => doc.fitContentOnce = true + }], ['clusters', { waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire checkResult: (doc:Doc) => BoolCast(doc._freeform_useClusters, false), diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 91eac675f..90ec212fc 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -29,6 +29,7 @@ export enum ButtonType { DropdownList = 'dropdownList', DropdownButton = 'dropdownBtn', ClickButton = 'clickBtn', + IconClickButton = 'iconClickBtn', ToggleButton = 'toggleBtn', ColorButton = 'colorBtn', ToolButton = 'toolBtn', @@ -346,6 +347,32 @@ export class FontIconBox extends DocComponent() { ); } + @computed get iconClickButton() { + // Determine the type of toggle button + const buttonText: string = StrCast(this.rootDoc.buttonText); + const tooltip: string = StrCast(this.rootDoc.toolTip); + + const script = ScriptCast(this.rootDoc.onClick); + const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; + // Colors + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + + return ( + script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })} + /> + ); + } + /** * Default */ @@ -393,6 +420,7 @@ export class FontIconBox extends DocComponent() { case ButtonType.DropdownButton: return this.dropdownButton; case ButtonType.MultiToggleButton: return this.multiToggleButton; case ButtonType.ToggleButton: return this.toggleButton; + case ButtonType.IconClickButton: return this.iconClickButton; case ButtonType.ClickButton: case ButtonType.ToolButton: return ; case ButtonType.TextButton: return
))} + {!this.loading && ( + <> + } color={StrCast(Doc.UserDoc().userVariantColor)} /> + + )} ); }; @@ -227,43 +269,6 @@ export class GPTPopup extends React.Component { ); - editBox = () => { - const hr = this.highlightRange; - return ( - <> -
- {this.heading('TEXT EDIT SUGGESTIONS')} -
- {hr && ( -
- {this.text.slice(0, hr[0])} {this.text.slice(hr[0], hr[1])} {this.text.slice(hr[1])} -
- )} -
-
- {hr && !this.loading && ( - <> -
- <> - - - -
- {this.aiWarning()} - - )} - - ); - }; - aiWarning = () => this.done ? (
@@ -277,14 +282,14 @@ export class GPTPopup extends React.Component { heading = (headingText: string) => (
- {this.loading ? : } onClick={() => this.setVisible(false)} />} + {this.loading ? : } onClick={() => this.setVisible(false)} />}
); render() { return (
- {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.editBox()} + {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <>}
); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index c9fee4813..1319a236d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -424,10 +424,6 @@ export class PDFViewer extends React.Component { // Changing which document to add the annotation to (the currently selected PDF) GPTPopup.Instance.setSidebarId('data_sidebar'); - const anchor = this._getAnchor(undefined, false); - if (anchor) { - GPTPopup.Instance.setTargetAnchor(anchor); - } GPTPopup.Instance.addDoc = this.props.sidebarAddDoc; }; -- cgit v1.2.3-70-g09d2 From 592e1a1b1000157db77bd812b8debfbcc45844f9 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 17 Aug 2023 17:52:12 -0400 Subject: revert --- src/client/views/nodes/ImageBox.tsx | 4 +- .../views/nodes/generativeFill/GenerativeFill.tsx | 115 ++++++++------------- .../generativeFillUtils/ImageHandler.ts | 2 + 3 files changed, 46 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 352e0fbdc..603994016 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -252,8 +252,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - MainView.Instance.imageEditorOpen = true; - MainView.Instance.imageEditorSource = this.choosePath(field.url); + MainView.Instance.setImageEditorOpen(true); + MainView.Instance.setImageEditorSource(this.choosePath(field.url)); MainView.Instance.addDoc = this.props.addDocument; MainView.Instance.imageRootDoc = this.rootDoc; }), diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 99068d647..f136982bc 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -3,7 +3,7 @@ import React = require('react'); import { useEffect, useRef, useState } from 'react'; import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { BrushHandler } from './generativeFillUtils/BrushHandler'; -import { Box, Checkbox, FormControlLabel, IconButton, Slider, TextField } from '@mui/material'; +import { Box, IconButton, Slider, TextField } from '@mui/material'; import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces'; import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; @@ -19,7 +19,6 @@ import { Cast, DocCast, NumCast } from '../../../../fields/Types'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { OpenWhere, OpenWhereMod } from '../DocumentView'; import { Oval } from 'react-loader-spinner'; -import { CheckBox } from '../../search/CheckBox'; /** * For images not 1024x1024 fill in the rest in solid black, or a @@ -73,7 +72,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [saveLoading, setSaveLoading] = useState(false); - const [isNewCollection, setIsNewCollection] = useState(false); // the current image in the main canvas const currImg = useRef(null); // the unedited version of each generation (parent) @@ -83,6 +81,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // stores redo stack const redoStack = useRef([]); + // early stage properly, likely will get rid of + const freeformPosition = useRef([0, 0]); + // which urls were already saved to canvas const savedSrcs = useRef>(new Set()); @@ -90,6 +91,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const newCollectionRef = useRef(null); const parentDoc = useRef(null); const childrenDocs = useRef([]); + const addToExistingCollection = useRef(false); // Undo and Redo const handleUndo = () => { @@ -109,13 +111,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }; const handleRedo = () => { - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx || !currImg.current || !canvasRef.current) return; - + // TODO: handle undo as well const target = redoStack.current[redoStack.current.length - 1]; if (!target) { } else { - undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()]; const img = new Image(); img.src = target; ImageUtility.drawImgToCanvas(img, canvasRef); @@ -185,10 +184,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD console.log('first load'); if (!imageEditorSource || imageEditorSource === '') return; const img = new Image(); + console.log('source', imageEditorSource); img.src = imageEditorSource; + console.log('drawing image'); ImageUtility.drawImgToCanvas(img, canvasRef); currImg.current = img; originalImg.current = img; + freeformPosition.current = [0, 0]; return () => { console.log('cleanup'); @@ -197,6 +199,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD childrenDocs.current = []; currImg.current = null; originalImg.current = null; + freeformPosition.current = [0, 0]; undoStack.current = []; redoStack.current = []; }; @@ -265,25 +268,24 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // create first image if (!newCollectionRef.current) { - if (!isNewCollection) { - // newcollection should stay null - } else { - if (!(originalImg.current && imageRootDoc)) return; - // create new collection and add it to the view - newCollectionRef.current = Docs.Create.FreeformDocument([], { - x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, - y: NumCast(imageRootDoc.y), - _width: newCollectionSize, - _height: newCollectionSize, - title: 'Image edit collection', - }); - DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); - // add the doc to the main freeform - addDoc?.(newCollectionRef.current); - await createNewImgDoc(originalImg.current, true); + if (addToExistingCollection.current) { } + if (!(originalImg.current && imageRootDoc)) return; + console.log('creating first image'); + // create new collection and add it to the view + newCollectionRef.current = Docs.Create.FreeformDocument([], { + x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, + y: NumCast(imageRootDoc.y), + _width: newCollectionSize, + _height: newCollectionSize, + title: 'Image edit collection', + }); + DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); + // add the doc to the main freeform + // addDoc?.(newCollectionRef.current); + await createNewImgDoc(originalImg.current, true); } else { - // parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; + parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; childrenDocs.current = []; } @@ -292,24 +294,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const { urls } = res as APISuccess; const image = new Image(); image.src = urls[0]; - - // need to crop images agh - // save to dash - - // const imageRes = await Promise.all( - // urls.map(async url => { - // const newImg = new Image(); - // newImg.src = url; - // await onSave(newImg); - // }) - // ); - - // map each url to [url, imgDoc] setEdits(urls); - ImageUtility.drawImgToCanvas(image, canvasRef); currImg.current = image; - // onSave(currImg.current); + onSave(); + freeformPosition.current[0] += 1; + freeformPosition.current[1] = 0; } catch (err) { console.log(err); } @@ -336,7 +326,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // creates a new image document and returns its reference const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise => { - if (!imageRootDoc) return; + if (!newCollectionRef.current || !imageRootDoc) return; const src = img.src; const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] }); const source = Utils.prepend(result.accessPaths.agnostic.client); @@ -344,6 +334,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD if (firstDoc) { const x = 0; const initialY = 0; + console.log('first doc'); const newImg = Docs.Create.ImageDocument(source, { x: x, @@ -354,19 +345,16 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD data_nativeHeight: result.nativeHeight, }); - if (isNewCollection && newCollectionRef.current) { - Doc.AddDocToList(newCollectionRef.current, undefined, newImg); - } else { - addDoc?.(newImg); - } - + Doc.AddDocToList(newCollectionRef.current, undefined, newImg); parentDoc.current = newImg; return newImg; } else { if (!parentDoc.current) return; const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX; // dummy position + console.log('creating child elements'); const initialY = 0; + const newImg = Docs.Create.ImageDocument(source, { x: x, y: initialY, @@ -377,26 +365,21 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }); childrenDocs.current.push(newImg); - - if (isNewCollection && newCollectionRef.current) { - Doc.AddDocToList(newCollectionRef.current, undefined, newImg); - } else { - addDoc?.(newImg); - } - DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit', link_displayLine: true }); adjustImgPositions(); + + Doc.AddDocToList(newCollectionRef.current, undefined, newImg); return newImg; } }; // need to maybe call on every img click, not just when the save btn is clicked - const onSave = async (img: HTMLImageElement) => { + const onSave = async () => { setSaveLoading(true); - // if (!currImg.current || !originalImg.current || !imageRootDoc) return; + if (!currImg.current || !originalImg.current || !imageRootDoc) return; try { console.log('creating another image'); - await createNewImgDoc(img, false); + await createNewImgDoc(currImg.current, false); } catch (err) { console.log(err); } @@ -406,7 +389,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const handleViewClose = () => { if (newCollectionRef.current) { newCollectionRef.current.fitContentOnce = true; - // CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); } MainView.Instance.setImageEditorOpen(false); MainView.Instance.setImageEditorSource(''); @@ -418,19 +401,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD

AI Image Editor

- { - setIsNewCollection(prev => !prev); - }} - /> - } - label={'Create New Collection'} - labelPlacement="end" - sx={{ whiteSpace: 'nowrap' }} - /> @@ -523,14 +493,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD height={100} src={edit} onClick={async () => { + // if (savedSrcs.current.has(edit)) return; const img = new Image(); img.src = edit; ImageUtility.drawImgToCanvas(img, canvasRef); currImg.current = img; savedSrcs.current.add(edit); - undoStack.current = []; - redoStack.current = []; - await onSave(img); + await onSave(); }} /> ))} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index 45cf7196b..1954ab3fb 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -101,7 +101,9 @@ export class ImageUtility { ctx.clearRect(0, 0, canvasSize, canvasSize); ctx.drawImage(img, 0, 0, width, height); } else { + console.log('loading image'); img.onload = () => { + console.log('loaded'); const ctx = this.getCanvasContext(canvasRef); if (!ctx) return; ctx.globalCompositeOperation = 'source-over'; -- cgit v1.2.3-70-g09d2 From 107f718199a5b0e15f62e5c7e7034b11047ff512 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 17 Aug 2023 19:11:42 -0400 Subject: baseline working version, different image sizes work --- .../views/nodes/generativeFill/GenerativeFill.tsx | 93 +++++++++------- .../generativeFillUtils/ImageHandler.ts | 119 +++++++++++++++++---- .../generativeFillUtils/generativeFillConstants.ts | 3 +- 3 files changed, 156 insertions(+), 59 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index f136982bc..4d475149d 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -42,10 +42,9 @@ enum BrushStyle { MARQUEE, } -interface ImageEdit { - imgElement: HTMLImageElement; - parent: ImageEdit; - children: ImageEdit[]; +interface ImageDimensions { + width: number; + height: number; } interface GenerativeFillProps { @@ -59,7 +58,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const canvasRef = useRef(null); const canvasBackgroundRef = useRef(null); const drawingAreaRef = useRef(null); - const fileRef = useRef(null); const [cursorData, setCursorData] = useState({ x: 0, y: 0, @@ -72,6 +70,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [saveLoading, setSaveLoading] = useState(false); + const [canvasDims, setCanvasDims] = useState({ + width: canvasSize, + height: canvasSize, + }); // the current image in the main canvas const currImg = useRef(null); // the unedited version of each generation (parent) @@ -91,7 +93,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const newCollectionRef = useRef(null); const parentDoc = useRef(null); const childrenDocs = useRef([]); - const addToExistingCollection = useRef(false); // Undo and Redo const handleUndo = () => { @@ -100,12 +101,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const target = undoStack.current[undoStack.current.length - 1]; if (!target) { - ImageUtility.drawImgToCanvas(currImg.current, canvasRef); + ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height); } else { redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()]; const img = new Image(); img.src = target; - ImageUtility.drawImgToCanvas(img, canvasRef); + ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); undoStack.current = undoStack.current.slice(0, -1); } }; @@ -117,7 +118,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD } else { const img = new Image(); img.src = target; - ImageUtility.drawImgToCanvas(img, canvasRef); + ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); redoStack.current = redoStack.current.slice(0, -1); } }; @@ -129,7 +130,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD ctx.clearRect(0, 0, canvasSize, canvasSize); undoStack.current = []; redoStack.current = []; - ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true); + ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasSize, canvasSize); }; // initiate brushing @@ -144,7 +145,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD setIsBrushing(true); const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale); - BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT); }; @@ -181,19 +181,21 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // first load useEffect(() => { - console.log('first load'); if (!imageEditorSource || imageEditorSource === '') return; const img = new Image(); - console.log('source', imageEditorSource); img.src = imageEditorSource; - console.log('drawing image'); - ImageUtility.drawImgToCanvas(img, canvasRef); currImg.current = img; originalImg.current = img; - freeformPosition.current = [0, 0]; + img.onload = () => { + const imgWidth = img.naturalWidth; + const imgHeight = img.naturalHeight; + const scale = Math.min(canvasSize / imgWidth, canvasSize / imgHeight); + const width = imgWidth * scale; + const height = imgHeight * scale; + setCanvasDims({ width, height }); + }; return () => { - console.log('cleanup'); newCollectionRef.current = null; parentDoc.current = null; childrenDocs.current = []; @@ -202,9 +204,15 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD freeformPosition.current = [0, 0]; undoStack.current = []; redoStack.current = []; + ImageUtility.clearCanvas(canvasRef); }; }, [canvasRef, imageEditorSource]); + useEffect(() => { + if (!currImg.current) return; + ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height); + }, [canvasDims]); + // handles brush sizing useEffect(() => { const handleKeyPress = (e: KeyboardEvent) => { @@ -261,15 +269,18 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD setLoading(true); // need to adjust later try { - const maskBlob = await ImageUtility.canvasToBlob(canvas); - const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img)); + const canvasOriginalImg = ImageUtility.getCanvasImg(img); + if (!canvasOriginalImg) return; + const canvasMask = ImageUtility.getCanvasMask(canvas); + if (!canvasMask) return; + // ImageUtility.downloadCanvas(canvasMask); + const maskBlob = await ImageUtility.canvasToBlob(canvasMask); + const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg); const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2); // const res = await ImageUtility.mockGetEdit(img.src); // create first image if (!newCollectionRef.current) { - if (addToExistingCollection.current) { - } if (!(originalImg.current && imageRootDoc)) return; console.log('creating first image'); // create new collection and add it to the view @@ -281,25 +292,30 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD title: 'Image edit collection', }); DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); + + // opening new tab + newCollectionRef.current.fitContentOnce = true; + CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + // add the doc to the main freeform // addDoc?.(newCollectionRef.current); await createNewImgDoc(originalImg.current, true); } else { - parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; + // parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; childrenDocs.current = []; } originalImg.current = currImg.current; const { urls } = res as APISuccess; + const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImageToCanvasDataURL(url, canvasDims.width, canvasDims.height))); const image = new Image(); - image.src = urls[0]; - setEdits(urls); - ImageUtility.drawImgToCanvas(image, canvasRef); + image.src = imgUrls[0]; + + setEdits(imgUrls); + ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height); currImg.current = image; onSave(); - freeformPosition.current[0] += 1; - freeformPosition.current[1] = 0; } catch (err) { console.log(err); } @@ -334,7 +350,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD if (firstDoc) { const x = 0; const initialY = 0; - console.log('first doc'); const newImg = Docs.Create.ImageDocument(source, { x: x, @@ -387,10 +402,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }; const handleViewClose = () => { - if (newCollectionRef.current) { - newCollectionRef.current.fitContentOnce = true; - CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); - } + // if (newCollectionRef.current) { + // newCollectionRef.current.fitContentOnce = true; + // CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + // } MainView.Instance.setImageEditorOpen(false); MainView.Instance.setImageEditorSource(''); setEdits([]); @@ -420,8 +435,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD onPointerMove={updateCursorData} onPointerDown={handlePointerDown} onPointerUp={handlePointerUp}> - - + +
( { // if (savedSrcs.current.has(edit)) return; const img = new Image(); img.src = edit; - ImageUtility.drawImgToCanvas(img, canvasRef); + ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); currImg.current = img; savedSrcs.current.add(edit); await onSave(); @@ -519,15 +533,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD Original { if (!originalImg.current) return; const img = new Image(); img.src = originalImg.current.src; - ImageUtility.drawImgToCanvas(img, canvasRef); + ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); currImg.current = img; }} /> diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index 1954ab3fb..8d32221bd 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -1,5 +1,5 @@ import { RefObject } from 'react'; -import { canvasSize } from './generativeFillConstants'; +import { bgColor, canvasSize } from './generativeFillConstants'; export interface APISuccess { status: 'success'; @@ -22,6 +22,47 @@ export class ImageUtility { }); }; + // given a square api image, get the cropped img + static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => { + // Create a new canvas element + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (ctx) { + // Clear the canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + if (width < height) { + // horizontal padding, x offset + const xOffset = (canvasSize - width) / 2; + console.log(xOffset); + ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - height) / 2; + ctx.drawImage(img, 0, yOffset, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); + } + return canvas; + } + }; + + static convertImageToCanvasDataURL = async (imageSrc: string, width: number, height: number): Promise => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + const canvas = this.getCroppedImg(img, width, height); + if (canvas) { + const dataUrl = canvas.toDataURL(); + resolve(dataUrl); + } + }; + img.onerror = error => { + reject(error); + }; + img.src = imageSrc; + }); + }; + static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise => { const apiUrl = 'https://api.openai.com/v1/images/edits'; const fd = new FormData(); @@ -90,41 +131,83 @@ export class ImageUtility { }; }; - static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject, loaded?: boolean) => { - if (loaded) { + static clearCanvas = (canvasRef: React.RefObject) => { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx || !canvasRef.current) return; + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + }; + + static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject, width: number, height: number) => { + const drawImg = (img: HTMLImageElement) => { const ctx = this.getCanvasContext(canvasRef); if (!ctx) return; ctx.globalCompositeOperation = 'source-over'; - const scale = Math.max(canvasSize / img.width, canvasSize / img.height); - const width = img.width * scale; - const height = img.height * scale; - ctx.clearRect(0, 0, canvasSize, canvasSize); + ctx.clearRect(0, 0, width, height); ctx.drawImage(img, 0, 0, width, height); + }; + + if (img.complete) { + drawImg(img); } else { console.log('loading image'); img.onload = () => { - console.log('loaded'); - const ctx = this.getCanvasContext(canvasRef); - if (!ctx) return; - ctx.globalCompositeOperation = 'source-over'; - const scale = Math.max(canvasSize / img.width, canvasSize / img.height); - const width = img.width * scale; - const height = img.height * scale; - ctx.clearRect(0, 0, canvasSize, canvasSize); - ctx.drawImage(img, 0, 0, width, height); + drawImg(img); }; } }; // The image must be loaded! - static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement => { + static getCanvasMask = (srcCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => { const canvas = document.createElement('canvas'); canvas.width = canvasSize; canvas.height = canvasSize; const ctx = canvas.getContext('2d'); + if (!ctx) return; ctx?.clearRect(0, 0, canvasSize, canvasSize); - ctx?.drawImage(img, 0, 0, canvasSize, canvasSize); + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, canvasSize, canvasSize); + // extract and set padding data + if (srcCanvas.height > srcCanvas.width) { + // horizontal padding, x offset + const xOffset = (canvasSize - srcCanvas.width) / 2; + ctx?.clearRect(xOffset, 0, srcCanvas.width, srcCanvas.height); + ctx.drawImage(srcCanvas, xOffset, 0, srcCanvas.width, srcCanvas.height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - srcCanvas.height) / 2; + ctx?.clearRect(0, yOffset, srcCanvas.width, srcCanvas.height); + ctx.drawImage(srcCanvas, 0, yOffset, srcCanvas.width, srcCanvas.height); + } + return canvas; + }; + + // The image must be loaded! + static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + // fix scaling + const scale = Math.min(canvasSize / img.width, canvasSize / img.height); + const width = img.width * scale; + const height = img.height * scale; + ctx?.clearRect(0, 0, canvasSize, canvasSize); + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, canvasSize, canvasSize); + + // extract and set padding data + if (img.naturalHeight > img.naturalWidth) { + // horizontal padding, x offset + const xOffset = (canvasSize - width) / 2; + + ctx.drawImage(img, xOffset, 0, width, height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - height) / 2; + ctx.drawImage(img, 0, yOffset, width, height); + } return canvas; }; } diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts index 412a4d238..286dc6e4c 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts @@ -1,9 +1,10 @@ // constants export const canvasSize = 512; export const freeformRenderSize = 300; -export const offsetDistanceY = freeformRenderSize + 200; +export const offsetDistanceY = freeformRenderSize + 300; export const offsetX = 200; export const newCollectionSize = 500; export const activeColor = '#1976d2'; export const eraserColor = '#e1e9ec'; +export const bgColor = '#f0f4f6'; -- cgit v1.2.3-70-g09d2 From 1f44bed14529f8bfc280f714ffd794813c1b7a73 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 17 Aug 2023 21:02:33 -0400 Subject: baseline before changes --- src/client/apis/gpt/GPT.ts | 1 - .../views/nodes/generativeFill/GenerativeFill.tsx | 61 ++++++---------------- .../generativeFillUtils/ImageHandler.ts | 10 ++-- .../generativeFillUtils/generativeFillConstants.ts | 2 +- .../generativeFillInterfaces.ts | 19 ++++--- 5 files changed, 32 insertions(+), 61 deletions(-) (limited to 'src') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 6b4106f56..6bde7989b 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -39,7 +39,6 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => { temperature: opts.temp, prompt: `${opts.prompt}${inputText}`, }); - // console.log(response.data.choices[0]); return response.data.choices[0].text; } catch (err) { console.log(err); diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 4d475149d..97d1cbf20 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -4,37 +4,21 @@ import { useEffect, useRef, useState } from 'react'; import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { BrushHandler } from './generativeFillUtils/BrushHandler'; import { Box, IconButton, Slider, TextField } from '@mui/material'; -import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces'; +import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces'; import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; import { BsEraser, BsX } from 'react-icons/bs'; import { CiUndo, CiRedo } from 'react-icons/ci'; -import Buttons from './GenerativeFillButtons'; import { MainView } from '../../MainView'; import { Doc } from '../../../../fields/Doc'; import { Networking } from '../../../Network'; import { Utils } from '../../../../Utils'; import { DocUtils, Docs } from '../../../documents/Documents'; -import { Cast, DocCast, NumCast } from '../../../../fields/Types'; +import { NumCast } from '../../../../fields/Types'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; -import { OpenWhere, OpenWhereMod } from '../DocumentView'; +import { OpenWhereMod } from '../DocumentView'; import { Oval } from 'react-loader-spinner'; - -/** - * For images not 1024x1024 fill in the rest in solid black, or a - * reflected version of the image. - * - * add a branch from image directly checkbox - */ - -/** - * - * - * CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right); - * CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent) - * mind mapping - * this.props.addDocTab(); - */ +import Buttons from './GenerativeFillButtons'; enum BrushStyle { ADD, @@ -42,11 +26,6 @@ enum BrushStyle { MARQUEE, } -interface ImageDimensions { - width: number; - height: number; -} - interface GenerativeFillProps { imageEditorOpen: boolean; imageEditorSource: string; @@ -112,10 +91,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }; const handleRedo = () => { - // TODO: handle undo as well + const ctx = ImageUtility.getCanvasContext(canvasRef); + if (!ctx || !currImg.current || !canvasRef.current) return; + const target = redoStack.current[redoStack.current.length - 1]; if (!target) { } else { + undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()]; const img = new Image(); img.src = target; ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); @@ -123,6 +105,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD } }; + // resets any erase strokes const handleReset = () => { if (!canvasRef.current || !currImg.current) return; const ctx = ImageUtility.getCanvasContext(canvasRef); @@ -130,7 +113,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD ctx.clearRect(0, 0, canvasSize, canvasSize); undoStack.current = []; redoStack.current = []; - ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasSize, canvasSize); + ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height); }; // initiate brushing @@ -236,7 +219,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD e.preventDefault(); e.stopPropagation(); const delta = e.deltaY; - const scaleFactor = delta > 0 ? 0.98 : 1.02; // Adjust the scale factor as per your requirement + const scaleFactor = delta > 0 ? 0.98 : 1.02; setCanvasScale(prevScale => prevScale * scaleFactor); }; @@ -267,13 +250,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const ctx = ImageUtility.getCanvasContext(canvasRef); if (!ctx) return; setLoading(true); - // need to adjust later try { const canvasOriginalImg = ImageUtility.getCanvasImg(img); if (!canvasOriginalImg) return; const canvasMask = ImageUtility.getCanvasMask(canvas); if (!canvasMask) return; - // ImageUtility.downloadCanvas(canvasMask); const maskBlob = await ImageUtility.canvasToBlob(canvasMask); const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg); const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2); @@ -282,7 +263,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // create first image if (!newCollectionRef.current) { if (!(originalImg.current && imageRootDoc)) return; - console.log('creating first image'); // create new collection and add it to the view newCollectionRef.current = Docs.Create.FreeformDocument([], { x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, @@ -294,19 +274,17 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); // opening new tab - newCollectionRef.current.fitContentOnce = true; CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + newCollectionRef.current.fitContentOnce = true; // add the doc to the main freeform - // addDoc?.(newCollectionRef.current); await createNewImgDoc(originalImg.current, true); } else { - // parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; + parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; childrenDocs.current = []; } originalImg.current = currImg.current; - const { urls } = res as APISuccess; const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImageToCanvasDataURL(url, canvasDims.width, canvasDims.height))); const image = new Image(); @@ -366,8 +344,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD } else { if (!parentDoc.current) return; const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX; - // dummy position - console.log('creating child elements'); const initialY = 0; const newImg = Docs.Create.ImageDocument(source, { @@ -388,12 +364,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD } }; - // need to maybe call on every img click, not just when the save btn is clicked const onSave = async () => { setSaveLoading(true); if (!currImg.current || !originalImg.current || !imageRootDoc) return; try { - console.log('creating another image'); await createNewImgDoc(currImg.current, false); } catch (err) { console.log(err); @@ -402,10 +376,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }; const handleViewClose = () => { - // if (newCollectionRef.current) { - // newCollectionRef.current.fitContentOnce = true; - // CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); - // } MainView.Instance.setImageEditorOpen(false); MainView.Instance.setImageEditorSource(''); setEdits([]); @@ -435,8 +405,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD onPointerMove={updateCursorData} onPointerDown={handlePointerDown} onPointerUp={handlePointerUp}> - - + +
{ - // if (savedSrcs.current.has(edit)) return; const img = new Image(); img.src = edit; ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index 8d32221bd..4847bfeed 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -35,7 +35,6 @@ export class ImageUtility { if (width < height) { // horizontal padding, x offset const xOffset = (canvasSize - width) / 2; - console.log(xOffset); ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); } else { // vertical padding, y offset @@ -156,7 +155,7 @@ export class ImageUtility { } }; - // The image must be loaded! + // The image must be loaded static getCanvasMask = (srcCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => { const canvas = document.createElement('canvas'); canvas.width = canvasSize; @@ -164,7 +163,7 @@ export class ImageUtility { const ctx = canvas.getContext('2d'); if (!ctx) return; ctx?.clearRect(0, 0, canvasSize, canvasSize); - ctx.fillStyle = bgColor; + ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, canvasSize, canvasSize); // extract and set padding data @@ -182,7 +181,7 @@ export class ImageUtility { return canvas; }; - // The image must be loaded! + // The image must be loaded static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => { const canvas = document.createElement('canvas'); canvas.width = canvasSize; @@ -194,10 +193,9 @@ export class ImageUtility { const width = img.width * scale; const height = img.height * scale; ctx?.clearRect(0, 0, canvasSize, canvasSize); - ctx.fillStyle = bgColor; + ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, canvasSize, canvasSize); - // extract and set padding data if (img.naturalHeight > img.naturalWidth) { // horizontal padding, x offset const xOffset = (canvasSize - width) / 2; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts index 286dc6e4c..1fe151b46 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts @@ -1,7 +1,7 @@ // constants export const canvasSize = 512; export const freeformRenderSize = 300; -export const offsetDistanceY = freeformRenderSize + 300; +export const offsetDistanceY = freeformRenderSize + 400; export const offsetX = 200; export const newCollectionSize = 500; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts index 9b9b9d3c2..83a21a1a5 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts @@ -1,16 +1,21 @@ // interfaces export interface CursorData { - x: number; - y: number; - width: number; + x: number; + y: number; + width: number; } export interface Point { - x: number; - y: number; + x: number; + y: number; } export enum BrushMode { - ADD, - SUBTRACT, + ADD, + SUBTRACT, +} + +export interface ImageDimensions { + width: number; + height: number; } -- cgit v1.2.3-70-g09d2 From bce4589c0bd871437c83bf44a6a818d01b9bf0f2 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Fri, 18 Aug 2023 16:52:12 -0400 Subject: reflected image padding --- .../views/nodes/generativeFill/GenerativeFill.tsx | 141 +++++++++++++-------- .../nodes/generativeFill/GenerativeFillButtons.tsx | 11 +- .../generativeFillUtils/ImageHandler.ts | 88 +++++++++++-- 3 files changed, 163 insertions(+), 77 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 97d1cbf20..92ce3a8a8 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -3,22 +3,23 @@ import React = require('react'); import { useEffect, useRef, useState } from 'react'; import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { BrushHandler } from './generativeFillUtils/BrushHandler'; -import { Box, IconButton, Slider, TextField } from '@mui/material'; +import { Box, Checkbox, FormControlLabel, IconButton, Slider, TextField } from '@mui/material'; import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces'; import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; import { BsEraser, BsX } from 'react-icons/bs'; import { CiUndo, CiRedo } from 'react-icons/ci'; import { MainView } from '../../MainView'; -import { Doc } from '../../../../fields/Doc'; +import { Doc, DocListCast } from '../../../../fields/Doc'; import { Networking } from '../../../Network'; -import { Utils } from '../../../../Utils'; +import { Utils, returnEmptyDoclist } from '../../../../Utils'; import { DocUtils, Docs } from '../../../documents/Documents'; -import { NumCast } from '../../../../fields/Types'; +import { DocCast, NumCast } from '../../../../fields/Types'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { OpenWhereMod } from '../DocumentView'; import { Oval } from 'react-loader-spinner'; import Buttons from './GenerativeFillButtons'; +import { List } from '../../../../fields/List'; enum BrushStyle { ADD, @@ -33,6 +34,8 @@ interface GenerativeFillProps { addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined; } +// New field on image doc: gen_fill_children => list of children Docs + const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => { const canvasRef = useRef(null); const canvasBackgroundRef = useRef(null); @@ -44,15 +47,18 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD }); const [isBrushing, setIsBrushing] = useState(false); const [canvasScale, setCanvasScale] = useState(0.5); - const [edits, setEdits] = useState([]); + // format: array of [image source, corresponding image Doc] + const [edits, setEdits] = useState<(string | Doc)[][]>([]); + const [edited, setEdited] = useState(false); const [brushStyle, setBrushStyle] = useState(BrushStyle.ADD); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); - const [saveLoading, setSaveLoading] = useState(false); const [canvasDims, setCanvasDims] = useState({ width: canvasSize, height: canvasSize, }); + // whether to create a new collection or not + const [isNewCollection, setIsNewCollection] = useState(true); // the current image in the main canvas const currImg = useRef(null); // the unedited version of each generation (parent) @@ -62,12 +68,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // stores redo stack const redoStack = useRef([]); - // early stage properly, likely will get rid of - const freeformPosition = useRef([0, 0]); - - // which urls were already saved to canvas - const savedSrcs = useRef>(new Set()); - // references to keep track of tree structure const newCollectionRef = useRef(null); const parentDoc = useRef(null); @@ -178,13 +178,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD setCanvasDims({ width, height }); }; + // cleanup return () => { + setEdited(false); newCollectionRef.current = null; parentDoc.current = null; childrenDocs.current = []; currImg.current = null; originalImg.current = null; - freeformPosition.current = [0, 0]; undoStack.current = []; redoStack.current = []; ImageUtility.clearCanvas(canvasRef); @@ -250,10 +251,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const ctx = ImageUtility.getCanvasContext(canvasRef); if (!ctx) return; setLoading(true); + setEdited(true); try { const canvasOriginalImg = ImageUtility.getCanvasImg(img); if (!canvasOriginalImg) return; - const canvasMask = ImageUtility.getCanvasMask(canvas); + const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg); if (!canvasMask) return; const maskBlob = await ImageUtility.canvasToBlob(canvasMask); const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg); @@ -262,38 +264,46 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // create first image if (!newCollectionRef.current) { - if (!(originalImg.current && imageRootDoc)) return; - // create new collection and add it to the view - newCollectionRef.current = Docs.Create.FreeformDocument([], { - x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, - y: NumCast(imageRootDoc.y), - _width: newCollectionSize, - _height: newCollectionSize, - title: 'Image edit collection', - }); - DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); - - // opening new tab - CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); - newCollectionRef.current.fitContentOnce = true; - - // add the doc to the main freeform - await createNewImgDoc(originalImg.current, true); + if (!isNewCollection && imageRootDoc) { + // new collection stays null + parentDoc.current = imageRootDoc; + } else { + if (!(originalImg.current && imageRootDoc)) return; + // create new collection and add it to the view + newCollectionRef.current = Docs.Create.FreeformDocument([], { + x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, + y: NumCast(imageRootDoc.y), + _width: newCollectionSize, + _height: newCollectionSize, + title: 'Image edit collection', + }); + DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); + + // opening new tab + CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); + + // add the doc to the main freeform + await createNewImgDoc(originalImg.current, true); + } } else { - parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1]; childrenDocs.current = []; } originalImg.current = currImg.current; const { urls } = res as APISuccess; - const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImageToCanvasDataURL(url, canvasDims.width, canvasDims.height))); + const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height))); + const imgRes = await Promise.all( + imgUrls.map(async url => { + const saveRes = await onSave(url); + return [url, saveRes as Doc]; + }) + ); + setEdits(imgRes); const image = new Image(); image.src = imgUrls[0]; - setEdits(imgUrls); ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height); currImg.current = image; - onSave(); } catch (err) { console.log(err); } @@ -320,7 +330,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // creates a new image document and returns its reference const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise => { - if (!newCollectionRef.current || !imageRootDoc) return; + if (!imageRootDoc) return; const src = img.src; const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] }); const source = Utils.prepend(result.accessPaths.agnostic.client); @@ -337,8 +347,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD data_nativeWidth: result.nativeWidth, data_nativeHeight: result.nativeHeight, }); - - Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + // add a new doc list field to newimg + if (isNewCollection && newCollectionRef.current) { + Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + } else { + addDoc?.(newImg); + } parentDoc.current = newImg; return newImg; } else { @@ -354,30 +368,40 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD data_nativeWidth: result.nativeWidth, data_nativeHeight: result.nativeHeight, }); - + newImg.gen_fill_children = new List([]); childrenDocs.current.push(newImg); - DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit', link_displayLine: true }); + // DocListCast(parentDoc.current.gen_fill_children).push(newImg); + DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true }); adjustImgPositions(); - Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + if (isNewCollection && newCollectionRef.current) { + Doc.AddDocToList(newCollectionRef.current, undefined, newImg); + } else { + addDoc?.(newImg); + } return newImg; } }; - const onSave = async () => { - setSaveLoading(true); + const onSave = async (src: string) => { + const img = new Image(); + img.src = src; if (!currImg.current || !originalImg.current || !imageRootDoc) return; try { - await createNewImgDoc(currImg.current, false); + const res = await createNewImgDoc(img, false); + return res; } catch (err) { console.log(err); } - setSaveLoading(false); }; const handleViewClose = () => { MainView.Instance.setImageEditorOpen(false); MainView.Instance.setImageEditorSource(''); + if (newCollectionRef.current) { + newCollectionRef.current.fitContentOnce = true; + } + setEdits([]); }; @@ -386,15 +410,25 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD

AI Image Editor

+ { + setIsNewCollection(prev => !prev); + }} + /> + } + label={'Create New Collection'} + labelPlacement="end" + sx={{ whiteSpace: 'nowrap' }} + /> - {saveLoading && ( - - Saving image... - - )}
{/* Main canvas for editing */} @@ -475,14 +509,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD { const img = new Image(); - img.src = edit; + img.src = edit[0] as string; ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); currImg.current = img; - savedSrcs.current.add(edit); - await onSave(); + parentDoc.current = edit[1] as Doc; }} /> ))} diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx index b4d56b408..e15af0a56 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx @@ -14,19 +14,10 @@ interface ButtonContainerProps { onReset: () => void; } -const Buttons = ({ canvasRef, currImg, loading, getEdit, onReset }: ButtonContainerProps) => { - +const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => { return (
- {/* */} - {/* */} - +
); }; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts index ace7ebcae..f4ec70fbc 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts @@ -3,16 +3,6 @@ import { eraserColor } from './generativeFillConstants'; import { Point } from './generativeFillInterfaces'; export class BrushHandler { - static brushCircle = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D) => { - ctx.globalCompositeOperation = 'destination-out'; - ctx.shadowColor = '#ffffffeb'; - ctx.shadowBlur = 5; - ctx.beginPath(); - ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); - ctx.fill(); - ctx.closePath(); - }; - static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => { ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = fillColor; @@ -24,15 +14,6 @@ export class BrushHandler { ctx.closePath(); }; - static createBrushPath = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D) => { - const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint); - - for (let i = 0; i < dist; i += 5) { - const s = i / dist; - BrushHandler.brushCircle(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx); - } - }; - static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => { const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint); diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts index 027b99a52..97e03ff20 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts @@ -1,11 +1,10 @@ -import { Point } from "./generativeFillInterfaces"; +import { Point } from './generativeFillInterfaces'; export class GenerativeFillMathHelpers { - // math helpers - static distanceBetween = (p1: Point, p2: Point) => { - return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); - }; - static angleBetween = (p1: Point, p2: Point) => { - return Math.atan2(p2.x - p1.x, p2.y - p1.y); - }; + static distanceBetween = (p1: Point, p2: Point) => { + return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); + }; + static angleBetween = (p1: Point, p2: Point) => { + return Math.atan2(p2.x - p1.x, p2.y - p1.y); + }; } diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index 4ff70c86c..c6ecedb6b 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -12,6 +12,11 @@ export interface APIError { } export class ImageUtility { + /** + * + * @param canvas Canvas to convert + * @returns Blob of canvas + */ static canvasToBlob = (canvas: HTMLCanvasElement): Promise => { return new Promise(resolve => { canvas.toBlob(blob => { @@ -45,6 +50,7 @@ export class ImageUtility { } }; + // converts an image to a canvas data url static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise => { return new Promise((resolve, reject) => { const img = new Image(); @@ -62,6 +68,7 @@ export class ImageUtility { }); }; + // calls the openai api to get image edits static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise => { const apiUrl = 'https://api.openai.com/v1/images/edits'; const fd = new FormData(); @@ -92,6 +99,7 @@ export class ImageUtility { } }; + // mock api call static mockGetEdit = async (mockSrc: string): Promise => { return { status: 'success', @@ -99,6 +107,7 @@ export class ImageUtility { }; }; + // Gets the canvas rendering context of a canvas static getCanvasContext = (canvasRef: RefObject): CanvasRenderingContext2D | null => { if (!canvasRef.current) return null; const ctx = canvasRef.current.getContext('2d'); @@ -106,6 +115,7 @@ export class ImageUtility { return ctx; }; + // Helper for downloading the canvas (for debugging) static downloadCanvas = (canvas: HTMLCanvasElement) => { const url = canvas.toDataURL(); const downloadLink = document.createElement('a'); @@ -116,6 +126,7 @@ export class ImageUtility { downloadLink.remove(); }; + // Download the canvas (for debugging) static downloadImageCanvas = (imgUrl: string) => { const img = new Image(); img.src = imgUrl; @@ -130,12 +141,14 @@ export class ImageUtility { }; }; + // Clears the canvas static clearCanvas = (canvasRef: React.RefObject) => { const ctx = this.getCanvasContext(canvasRef); if (!ctx || !canvasRef.current) return; ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); }; + // Draws the image to the current canvas static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject, width: number, height: number) => { const drawImg = (img: HTMLImageElement) => { const ctx = this.getCanvasContext(canvasRef); @@ -154,7 +167,7 @@ export class ImageUtility { } }; - // The image must be loaded! + // Gets the image mask for the openai endpoint static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => { const canvas = document.createElement('canvas'); canvas.width = canvasSize; @@ -162,8 +175,6 @@ export class ImageUtility { const ctx = canvas.getContext('2d'); if (!ctx) return; ctx?.clearRect(0, 0, canvasSize, canvasSize); - // ctx.fillStyle = bgColor; - // ctx.fillRect(0, 0, canvasSize, canvasSize); ctx.drawImage(paddedCanvas, 0, 0); // extract and set padding data @@ -181,6 +192,7 @@ export class ImageUtility { return canvas; }; + // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; @@ -209,6 +221,7 @@ export class ImageUtility { ctx.putImageData(imageData, 0, 0); }; + // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; @@ -237,7 +250,7 @@ export class ImageUtility { ctx.putImageData(imageData, 0, 0); }; - // The image must be loaded! + // Gets the unaltered (besides filling in padding) version of the image for the api call static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => { const canvas = document.createElement('canvas'); canvas.width = canvasSize; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts index 1fe151b46..dc94a9368 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts @@ -1,4 +1,3 @@ -// constants export const canvasSize = 512; export const freeformRenderSize = 300; export const offsetDistanceY = freeformRenderSize + 400; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts index 83a21a1a5..1e7801056 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts @@ -1,4 +1,3 @@ -// interfaces export interface CursorData { x: number; y: number; -- cgit v1.2.3-70-g09d2 From 6913400a92d518989e831926567429c31703e4b4 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Fri, 18 Aug 2023 18:46:27 -0400 Subject: cleanup --- .../views/nodes/formattedText/FormattedTextBox.tsx | 20 -------------------- 1 file changed, 20 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2562c48e4..da277826a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -907,7 +907,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (resIndex < newText.length) { const marks = this._editorView?.state.storedMarks ?? []; - // if (!marks) return; this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks)); setTimeout(() => { this.animateRes(resIndex + 1, newText); @@ -915,19 +914,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - // const state = this._editorView?.state; - // if (!state) return; - // const to = state.selection.to; - // const updated = TextSelection.create(state.doc, to, to); - // this._editorView?.dispatch(state.tr.setSelection(updated).insertText('\n', to)); - // this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText('\nTesting').setStoredMarks(marks)); - // console.log('After ', this._editorView?.state.storedMarks); try { let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION); if (!res) { @@ -1243,13 +1230,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Fri, 18 Aug 2023 19:20:55 -0400 Subject: fix original image click --- src/client/views/nodes/generativeFill/GenerativeFill.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 998961f20..ce6ce672f 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -63,6 +63,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD const currImg = useRef(null); // the unedited version of each generation (parent) const originalImg = useRef(null); + const originalDoc = useRef(null); // stores history of data urls const undoStack = useRef([]); // stores redo stack @@ -186,6 +187,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD childrenDocs.current = []; currImg.current = null; originalImg.current = null; + originalDoc.current = null; undoStack.current = []; redoStack.current = []; ImageUtility.clearCanvas(canvasRef); @@ -291,6 +293,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD } originalImg.current = currImg.current; + originalDoc.current = parentDoc.current; const { urls } = res as APISuccess; const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height))); const imgRes = await Promise.all( @@ -302,9 +305,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD setEdits(imgRes); const image = new Image(); image.src = imgUrls[0]; - ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height); currImg.current = image; + parentDoc.current = imgRes[0][1] as Doc; } catch (err) { console.log(err); } @@ -511,6 +514,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD key={i} width={75} src={edit[0] as string} + style={{ cursor: 'pointer' }} onClick={async () => { const img = new Image(); img.src = edit[0] as string; @@ -545,6 +549,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD img.src = originalImg.current.src; ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); currImg.current = img; + parentDoc.current = originalDoc.current; }} />
-- cgit v1.2.3-70-g09d2 From c9fe812391b68b1337f50d4e572a18640e39ff3b Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Fri, 18 Aug 2023 19:50:18 -0400 Subject: changed dims to 1024 --- .../views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts | 2 +- .../nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index c6ecedb6b..2ede625f6 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -75,7 +75,7 @@ export class ImageUtility { fd.append('image', imgBlob, 'image.png'); fd.append('mask', maskBlob, 'mask.png'); fd.append('prompt', prompt); - fd.append('size', '512x512'); + fd.append('size', '1024x1024'); fd.append('n', n ? JSON.stringify(n) : '1'); fd.append('response_format', 'b64_json'); diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts index dc94a9368..4772304bc 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts @@ -1,4 +1,4 @@ -export const canvasSize = 512; +export const canvasSize = 1024; export const freeformRenderSize = 300; export const offsetDistanceY = freeformRenderSize + 400; export const offsetX = 200; -- cgit v1.2.3-70-g09d2 From 63c2c903d4d92aaeb0d024f02d8142d7c8ad7a5f Mon Sep 17 00:00:00 2001 From: geireann Date: Tue, 22 Aug 2023 15:32:06 -0400 Subject: tweaks --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 91a78c2af..f5cc1eb53 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1600,7 +1600,7 @@ export class CollectionFreeFormView extends CollectionSubView this.rootDoc.fitContentOnce, fitContentOnce => { if (fitContentOnce) this.fitContentOnce(); - this.rootDoc.fitContentOnce = false; + this.rootDoc.fitContentOnce = undefined; }, { fireImmediately: true, name: 'fitContent' } ); diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 0e230e19b..a2cf3bc25 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -426,7 +426,7 @@ export class FontIconBox extends DocComponent() { case ButtonType.DropdownButton: return this.dropdownButton; case ButtonType.MultiToggleButton: return this.multiToggleButton; case ButtonType.ToggleButton: return this.toggleButton; - case ButtonType.IconClickButton: return this.iconClickButton; + case ButtonType.IconClickButton: return this.iconClickButton; case ButtonType.ClickButton: case ButtonType.ToolButton: return ; case ButtonType.TextButton: return
{this.getDashboards(this.selectedDashboardGroup).map(dashboard => { - const href = ImageCast(dashboard.thumb)?.url.href; + const href = ImageCast(dashboard.thumb)?.url?.href; const shared = Object.keys(dashboard[DocAcl]) .filter(key => key !== `acl-${Doc.CurrentUserEmailNormalized}` && !['acl-Me', 'acl-Guest'].includes(key)) .some(key => dashboard[DocAcl][key] !== AclPrivate); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 95f88f14e..e15d57306 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -551,7 +551,7 @@ export class CollectionDockingView extends CollectionSubView() { }; render() { - const href = ImageCast(this.rootDoc.thumb)?.url.href; + const href = ImageCast(this.rootDoc.thumb)?.url?.href; return this.props.renderDepth > -1 ? (
{href ? ( diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index eb8064780..60d997120 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -42,7 +42,7 @@ var ForeignHtmlRenderer = function (styleSheets) { url = CorsProxy(new URL(webUrl).origin + inurl); } else if (!inurl.startsWith('http') && !inurl.startsWith('//')) { url = CorsProxy(webUrl + '/' + inurl); - } else if (inurl.startsWith('https')) { + } else if (inurl.startsWith('https') && !inurl.startsWith("https://dashblobstore.blob.core.windows.net")) { url = CorsProxy(inurl); } xhr.open('GET', url); -- cgit v1.2.3-70-g09d2 From f8c2d9d029f129eb595677b1e8a09ff1ebd5880c Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Wed, 23 Aug 2023 23:07:18 -0400 Subject: branching fix --- src/client/views/nodes/generativeFill/GenerativeFill.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index ce6ce672f..7230aa9b1 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -181,6 +181,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // cleanup return () => { + setInput(''); setEdited(false); newCollectionRef.current = null; parentDoc.current = null; @@ -268,8 +269,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // create first image if (!newCollectionRef.current) { if (!isNewCollection && imageRootDoc) { - // new collection stays null - parentDoc.current = imageRootDoc; + // if the parent hasn't been set yet + if (!parentDoc.current) parentDoc.current = imageRootDoc; } else { if (!(originalImg.current && imageRootDoc)) return; // create new collection and add it to the view -- cgit v1.2.3-70-g09d2 From 1a87cabca08f8919539644c439f38a9da9f7815e Mon Sep 17 00:00:00 2001 From: geireann Date: Wed, 23 Aug 2023 23:25:49 -0400 Subject: protecting against bad urls especially after generating thumbnails --- src/client/views/nodes/ImageBox.tsx | 5 +++-- src/client/views/nodes/WebBoxRenderer.js | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 44da98f75..6595689f7 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -287,10 +287,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent Date: Wed, 23 Aug 2023 23:38:19 -0400 Subject: fixed creating thumbnails to not break when one of the rendered items is an image stored on the same server. --- src/client/views/nodes/WebBoxRenderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index 321cf3638..425ef3e54 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -42,7 +42,7 @@ var ForeignHtmlRenderer = function (styleSheets) { url = CorsProxy(new URL(webUrl).origin + inurl); } else if (!inurl.startsWith('http') && !inurl.startsWith('//')) { url = CorsProxy(webUrl + '/' + inurl); - } else if (inurl.startsWith('https')) { + } else if (inurl.startsWith('https') && !inurl.startsWith(window.location.origin)) { url = CorsProxy(inurl); } xhr.open('GET', url); -- cgit v1.2.3-70-g09d2 From 9a9ee6058941dc89eb5719ef4fdd66a4743698fd Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 24 Aug 2023 10:37:07 -0400 Subject: removed iconClickButton and reverted fit All menu item --- src/client/util/CurrentUserUtils.ts | 14 ++++---- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 37 +++------------------- 2 files changed, 11 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9322cda4b..62b99d752 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -629,14 +629,14 @@ export class CurrentUserUtils { } static viewTools(): Button[] { return [ - { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true, - { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit all Docs to View (persistent)", btnType: ButtonType.IconClickButton, ignoreClick: false, expertMode: false, toolType:"viewAllPersist", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit all Docs to View (persistent)", btnType: ButtonType.ClickButton, ignoreClick: false, expertMode: false, toolType:"viewAllPersist", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } static textTools():Button[] { diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index a2cf3bc25..47dac651d 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -1,12 +1,13 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, MultiToggle, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { Button, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { Utils } from '../../../../Utils'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { SelectionManager } from '../../../util/SelectionManager'; import { undoable, UndoManager } from '../../../util/UndoManager'; @@ -14,14 +15,12 @@ import { ContextMenu } from '../../ContextMenu'; import { DocComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; +import { SelectedDocView } from '../../selectedDoc'; import { StyleProp } from '../../StyleProvider'; -import { FieldView, FieldViewProps } from '../FieldView'; import { OpenWhere } from '../DocumentView'; +import { FieldView, FieldViewProps } from '../FieldView'; import { RichTextMenu } from '../formattedText/RichTextMenu'; import './FontIconBox.scss'; -import { SelectedDocView } from '../../selectedDoc'; -import { Utils } from '../../../../Utils'; -import { FaAlignCenter, FaAlignJustify, FaAlignLeft, FaAlignRight } from 'react-icons/fa'; export enum ButtonType { TextButton = 'textBtn', @@ -29,7 +28,6 @@ export enum ButtonType { DropdownList = 'dropdownList', DropdownButton = 'dropdownBtn', ClickButton = 'clickBtn', - IconClickButton = 'iconClickBtn', ToggleButton = 'toggleBtn', ColorButton = 'colorBtn', ToolButton = 'toolBtn', @@ -353,32 +351,6 @@ export class FontIconBox extends DocComponent() { ); } - @computed get iconClickButton() { - // Determine the type of toggle button - const buttonText: string = StrCast(this.rootDoc.buttonText); - const tooltip: string = StrCast(this.rootDoc.toolTip); - - const script = ScriptCast(this.rootDoc.onClick); - const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; - // Colors - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - - return ( - script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })} - /> - ); - } - /** * Default */ @@ -426,7 +398,6 @@ export class FontIconBox extends DocComponent() { case ButtonType.DropdownButton: return this.dropdownButton; case ButtonType.MultiToggleButton: return this.multiToggleButton; case ButtonType.ToggleButton: return this.toggleButton; - case ButtonType.IconClickButton: return this.iconClickButton; case ButtonType.ClickButton: case ButtonType.ToolButton: return ; case ButtonType.TextButton: return
); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index c4b3f79a7..2c8e97512 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -36,8 +36,6 @@ import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; import { PinProps, PresBox } from './trails'; import React = require('react'); -import Color = require('color'); -import { MainView } from '../MainView'; export const pageSchema = createSchema({ googlePhotosUrl: 'string', @@ -56,6 +54,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent boolean) | undefined; + @action public static setImageEditorOpen(open: boolean) {ImageBox.imageEditorOpen = open;} + @action public static setImageEditorSource(source: string) {ImageBox.imageEditorSource = source;} private _ignoreScroll = false; private _forcedScroll = false; private _dropDisposer?: DragManager.DragDropDisposer; @@ -250,10 +255,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - MainView.Instance.setImageEditorOpen(true); - MainView.Instance.setImageEditorSource(this.choosePath(field.url)); - MainView.Instance.addDoc = this.props.addDocument; - MainView.Instance.imageRootDoc = this.rootDoc; + ImageBox.setImageEditorOpen(true); + ImageBox.setImageEditorSource(this.choosePath(field.url)); + ImageBox.addDoc = this.props.addDocument; + ImageBox.imageRootDoc = this.rootDoc; }), icon: 'pencil-alt', }); diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index 7230aa9b1..9c03600cf 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -20,6 +20,7 @@ import { OpenWhereMod } from '../DocumentView'; import Buttons from './GenerativeFillButtons'; import { List } from '../../../../fields/List'; import { CgClose } from 'react-icons/cg'; +import { ImageBox } from '../ImageBox'; enum BrushStyle { ADD, @@ -408,8 +409,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD // Closes the editor view const handleViewClose = () => { - MainView.Instance.setImageEditorOpen(false); - MainView.Instance.setImageEditorSource(''); + ImageBox.setImageEditorOpen(false); + ImageBox.setImageEditorSource(''); if (newCollectionRef.current) { newCollectionRef.current.fitContentOnce = true; } -- cgit v1.2.3-70-g09d2 From 50d65b4e805fd779400c710551648541b12ba3e6 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 24 Aug 2023 11:43:05 -0400 Subject: made link lines appear in the lightbox view --- src/client/views/MainView.tsx | 2 +- src/client/views/OverlayView.scss | 1 + src/client/views/OverlayView.tsx | 5 ++--- .../collections/collectionFreeForm/CollectionFreeFormLinksView.tsx | 5 ++++- 4 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e376c4fdf..490145546 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1006,12 +1006,12 @@ export class MainView extends React.Component { - {this.snapLines} + {/* */}
); diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 5362bf9f0..9b10d1cf7 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -4,6 +4,7 @@ top: 0; width: 100vw; height: 100vh; + z-index: 1001; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear /* background-color: pink; */ } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 339507f65..62aa4d1d4 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,3 +1,4 @@ + import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; @@ -7,14 +8,12 @@ import { Doc, DocListCast } from '../../fields/Doc'; import { Height, Width } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { NumCast } from '../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, setupMoveUpEvents, Utils } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; -import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView'; import { LightboxView } from './LightboxView'; -import { MainView } from './MainView'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; import './OverlayView.scss'; import { DefaultStyleProvider } from './StyleProvider'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 420e6a318..7c869af24 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -12,7 +12,10 @@ export class CollectionFreeFormLinksView extends React.Component { @computed get uniqueConnections() { return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)) .filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath))) - .map(c => ); + .map(c => { + console.log("got a connectoin", c) + return ; + }); } render() { -- cgit v1.2.3-70-g09d2 From 859d2d942b7766ed8052ff66be991e4d593982ff Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 24 Aug 2023 12:10:07 -0400 Subject: fixed gpt anno to not create an anchor unless actually requeted. --- src/client/views/nodes/WebBox.tsx | 5 ----- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 7 ------- 2 files changed, 12 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index a1e46b69b..febf8341e 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -358,11 +358,6 @@ export class WebBox extends ViewBoxAnnotatableComponent { this.sidebarId = id; }; - // pdfs and webpages - @observable - private targetAnchor: Doc | undefined; - @action - public setTargetAnchor = (anchor: Doc) => { - this.targetAnchor = anchor; - }; @observable private imgTargetDoc: Doc | undefined; -- cgit v1.2.3-70-g09d2 From 20e3d33d864f9ee9db2ca65848b0f42a087b699e Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 24 Aug 2023 12:12:56 -0400 Subject: updated dash version --- src/fields/DocSymbols.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index 088903082..856c377fa 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -24,4 +24,4 @@ export const Initializing = Symbol('DocInitializing'); export const ForceServerWrite = Symbol('DocForceServerWrite'); export const CachedUpdates = Symbol('DocCachedUpdates'); -export const DashVersion = 'v0.5.6'; +export const DashVersion = 'v0.5.7'; -- cgit v1.2.3-70-g09d2 From eeb7d43e9eca66c8e3418e89492848589ee20aa3 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 24 Aug 2023 15:48:26 -0400 Subject: speedups for dataviz --- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 8 +++++++- .../views/nodes/DataVizBox/components/PieChart.tsx | 9 ++++++--- .../views/nodes/DataVizBox/components/TableBox.tsx | 19 +++++++++---------- 3 files changed, 22 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index c07ab5ba1..284923092 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -15,6 +15,7 @@ import './DataVizBox.scss'; import { Histogram } from './components/Histogram'; import { PieChart } from './components/PieChart'; import { Toggle, ToggleType, Type } from 'browndash-components'; +import { Utils } from '../../../../Utils'; export enum DataVizView { TABLE = 'table', @@ -162,7 +163,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { if (DataVizBox.pairSet.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) return; DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []); fetch('/csvData?uri=' + this.dataUrl?.url.href) // - .then(res => res.json().then(action(res => !res.errno && DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res)))); + .then(res => res.json().then(action(res => { + if (!res.errno) { + DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res); + if (!this.dataDoc.dataViz_rowGuids && this.pairs) this.dataDoc.dataViz_rowGuids = new List(this.pairs.map(row => Utils.GenerateGuid())); + } + }))); } render() { diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 213baa8a4..e730d54f0 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -13,6 +13,7 @@ import { ColorPicker, EditableText, Size, Type } from 'browndash-components'; import { FaFillDrip } from 'react-icons/fa'; import { listSpec } from '../../../../../fields/Schema'; import { undoable } from '../../../../util/UndoManager'; +import { Utils } from '../../../../../Utils'; export interface PieChartProps { rootDoc: Doc; @@ -42,9 +43,11 @@ export class PieChart extends React.Component { private selectedData: any = undefined; // Selection of selected slice private hoverOverData: any = undefined; // Selection of slice being hovered over + @computed get rowGuids() { + return StrListCast(this.props.layoutDoc.dataViz_rowGuids) + } // filters all data to just display selected data if brushed (created from an incoming link) @computed get _piechartData() { - var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; @@ -52,7 +55,7 @@ export class PieChart extends React.Component { this.byCategory = false; } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: pair[this.props.axes[0]] })); } var ax0 = this.props.axes[0]; @@ -61,7 +64,7 @@ export class PieChart extends React.Component { this.byCategory = false; } return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) + ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)]))) .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] })); } diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 70483ac6f..e1c5aa125 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -33,9 +33,11 @@ interface TableBoxProps { export class TableBox extends React.Component { // filters all data to just display selected data if brushed (created from an incoming link) @computed get _tableData() { - if (this.incomingLinks.length! <= 0) return this.props.pairs; - var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); - return this.props.pairs?.filter(pair => this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)])); + if (this.incomingLinks.length <= 0) return this.props.pairs; + return this.props.pairs?.filter(pair => StrListCast(this.incomingLinks[0]?.dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)])); + } + @computed get rowGuids() { + return StrListCast(this.props.layoutDoc.dataViz_rowGuids) } @computed get incomingLinks() { @@ -47,9 +49,6 @@ export class TableBox extends React.Component { } @computed get columns() { - if (!this.props.layoutDoc.dataViz_rowGuids) this.props.layoutDoc.dataViz_rowGuids = new List(); - const guids = Cast(this.props.layoutDoc.dataViz_rowGuids, listSpec('string'), null); - if (guids.length == 0) this.props.pairs.map(row => guids.push(Utils.GenerateGuid())); return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header != '' && header != undefined) : []; } @@ -59,9 +58,9 @@ export class TableBox extends React.Component { const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null); const incomingSelected = this.incomingLinks.length ? StrListCast(this.incomingLinks[0].dataViz_selectedRows) : undefined; if (incomingSelected) { - selected.map(guid => { - if (!incomingSelected.includes(guid)) selected.splice(selected.indexOf(guid), 1); - }); // filters through selected to remove guids that were removed in the incoming data + const newsel = selected.filter(guid => incomingSelected.includes(guid));// filters through selected to remove guids that were removed in the incoming data + selected.length = 0; + selected.push(...newsel); } } @@ -145,7 +144,7 @@ export class TableBox extends React.Component { {this._tableData?.map((p, i) => { var containsData = false; - var guid = StrListCast(this.props.layoutDoc.dataViz_rowGuids)![this.props.pairs.indexOf(p)]; + var guid = this.rowGuids[this.props.pairs.indexOf(p)]; this.columns.map(col => { if (p[col] != '' && p[col] != null && p[col] != undefined) containsData = true; }); -- cgit v1.2.3-70-g09d2 From fa98e9f411ed19a752a912e42b6ed9607d151159 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 25 Aug 2023 01:55:09 -0400 Subject: optimizations to speed up pie/table viz --- src/client/documents/Documents.ts | 2 +- .../CollectionFreeFormLinksView.tsx | 5 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 177 +++++++-------- .../nodes/DataVizBox/components/Histogram.tsx | 164 ++++++-------- .../nodes/DataVizBox/components/LineChart.tsx | 30 +-- .../views/nodes/DataVizBox/components/PieChart.tsx | 169 +++++++------- .../views/nodes/DataVizBox/components/TableBox.tsx | 247 ++++++++++----------- 7 files changed, 369 insertions(+), 425 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e7d3e7efc..4933f0a7c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -680,7 +680,7 @@ export namespace Docs { DocumentType.DATAVIZ, { layout: { view: DataVizBox, dataField: defaultDataKey }, - options: { dataViz_title: '', _layout_fitWidth: true, nativeDimModifiable: true }, + options: { dataViz_title: '', dataViz: 'table', _layout_fitWidth: true, nativeDimModifiable: true }, }, ], [ diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 7c869af24..420e6a318 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -12,10 +12,7 @@ export class CollectionFreeFormLinksView extends React.Component { @computed get uniqueConnections() { return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)) .filter(c => !LightboxView.LightboxDoc || (LightboxView.IsLightboxDocView(c.a.docViewPath) && LightboxView.IsLightboxDocView(c.b.docViewPath))) - .map(c => { - console.log("got a connectoin", c) - return ; - }); + .map(c => ); } render() { diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 284923092..f9f241234 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,3 +1,4 @@ +import { Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, ObservableMap } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -9,13 +10,11 @@ import { Docs } from '../../../documents/Documents'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; import { PinProps } from '../trails'; +import { Histogram } from './components/Histogram'; import { LineChart } from './components/LineChart'; +import { PieChart } from './components/PieChart'; import { TableBox } from './components/TableBox'; import './DataVizBox.scss'; -import { Histogram } from './components/Histogram'; -import { PieChart } from './components/PieChart'; -import { Toggle, ToggleType, Type } from 'browndash-components'; -import { Utils } from '../../../../Utils'; export enum DataVizView { TABLE = 'table', @@ -30,19 +29,29 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { return FieldView.LayoutString(DataVizBox, fieldStr); } - // all data - static pairSet = new ObservableMap(); - @computed.struct get pairs() { - return DataVizBox.pairSet.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); - } + // all datasets that have been retrieved from the server stored as a map from the dataset url to an array of records + static dataset = new ObservableMap(); + private _vizRenderer: LineChart | Histogram | PieChart | undefined; - private _chartRenderer: LineChart | Histogram | PieChart | undefined; + // all CSV records in the dataset + @computed.struct get records() { + return DataVizBox.dataset.get(CsvCast(this.rootDoc[this.fieldKey]).url.href); + } - // current displayed chart type + // currently chosen visualization type: line, pie, histogram, table @computed get dataVizView(): DataVizView { return StrCast(this.layoutDoc._dataViz, 'table') as DataVizView; } + @computed get dataUrl() { + return Cast(this.dataDoc[this.fieldKey], CsvField); + } + @computed.struct get axes() { + return StrListCast(this.layoutDoc.dataViz_axes); + } + + selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List(axes)); + @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors restoreView = (data: Doc) => { const changedView = this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz); @@ -56,7 +65,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { this.layoutDoc['_' + key] = data[key]; } }); - const func = () => this._chartRenderer?.restoreView(data); + const func = () => this._vizRenderer?.restoreView(data); if (changedView || changedAxes) { setTimeout(func, 100); return true; @@ -66,7 +75,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => { const anchor = !pinProps ? this.rootDoc - : this._chartRenderer?.getAnchor(pinProps) ?? + : this._vizRenderer?.getAnchor(pinProps) ?? Docs.Create.ConfigDocument({ // when we clear selection -> we should have it so chartBox getAnchor returns undefined // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) @@ -87,92 +96,78 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { return anchor; }; - @computed.struct get axes() { - return StrListCast(this.layoutDoc.dataViz_axes); + componentDidMount() { + this.props.setContentView?.(this); + if (!DataVizBox.dataset.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) this.fetchData(); } - selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List(axes)); + + fetchData = () => { + DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests + fetch('/csvData?uri=' + this.dataUrl?.url.href) // + .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res)))); + }; // toggles for user to decide which chart type to view the data in - @computed get selectView() { + renderVizView = () => { const width = this.props.PanelWidth() * 0.9; const height = (this.props.PanelHeight() - 32) /* height of 'change view' button */ * 0.9; const margin = { top: 10, right: 25, bottom: 75, left: 45 }; - if (!this.pairs) return 'no data'; - switch (this.dataVizView) { - case DataVizView.TABLE: - return ; - case DataVizView.LINECHART: - return ( - (this._chartRenderer = r ?? undefined)} - height={height} - width={width} - fieldKey={this.fieldKey} - margin={margin} - rootDoc={this.rootDoc} - axes={this.axes} - pairs={this.pairs} - dataDoc={this.dataDoc} - /> - ); - case DataVizView.HISTOGRAM: - return ( - (this._chartRenderer = r ?? undefined)} - height={height} - width={width} - fieldKey={this.fieldKey} - margin={margin} - rootDoc={this.rootDoc} - axes={this.axes} - pairs={this.pairs} - dataDoc={this.dataDoc} - /> - ); - case DataVizView.PIECHART: - return ( - (this._chartRenderer = r ?? undefined)} - height={height} - width={width} - fieldKey={this.fieldKey} - margin={margin} - rootDoc={this.rootDoc} - axes={this.axes} - pairs={this.pairs} - dataDoc={this.dataDoc} - /> - ); + if (this.records) { + switch (this.dataVizView) { + case DataVizView.TABLE: + return ; + case DataVizView.LINECHART: + return ( + (this._vizRenderer = r ?? undefined)} + height={height} + width={width} + fieldKey={this.fieldKey} + margin={margin} + rootDoc={this.rootDoc} + axes={this.axes} + records={this.records} + dataDoc={this.dataDoc} + /> + ); + case DataVizView.HISTOGRAM: + return ( + (this._vizRenderer = r ?? undefined)} + height={height} + width={width} + fieldKey={this.fieldKey} + margin={margin} + rootDoc={this.rootDoc} + axes={this.axes} + records={this.records} + dataDoc={this.dataDoc} + /> + ); + case DataVizView.PIECHART: + return ( + (this._vizRenderer = r ?? undefined)} + height={height} + width={width} + fieldKey={this.fieldKey} + margin={margin} + rootDoc={this.rootDoc} + axes={this.axes} + records={this.records} + dataDoc={this.dataDoc} + /> + ); + } } - } - - @computed get dataUrl() { - return Cast(this.dataDoc[this.fieldKey], CsvField); - } - - componentDidMount() { - this.props.setContentView?.(this); - this.fetchData(); - if (!this.layoutDoc._dataViz) this.layoutDoc._dataViz = this.dataVizView; - } - - fetchData() { - if (DataVizBox.pairSet.has(CsvCast(this.rootDoc[this.fieldKey]).url.href)) return; - DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, []); - fetch('/csvData?uri=' + this.dataUrl?.url.href) // - .then(res => res.json().then(action(res => { - if (!res.errno) { - DataVizBox.pairSet.set(CsvCast(this.rootDoc[this.fieldKey]).url.href, res); - if (!this.dataDoc.dataViz_rowGuids && this.pairs) this.dataDoc.dataViz_rowGuids = new List(this.pairs.map(row => Utils.GenerateGuid())); - } - }))); - } + return 'no data/visualization'; + }; render() { - return !this.pairs?.length ? ( + return !this.records?.length ? ( // displays how to get data into the DataVizBox if its empty
To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.
) : ( @@ -195,7 +190,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() { (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz == DataVizView.HISTOGRAM} /> (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.PIECHART} />
- {this.selectView} + {this.renderVizView()}
); } diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index b3bdccbbb..48cbe5c5f 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -1,26 +1,26 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components'; +import * as d3 from 'd3'; +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, StrListCast } from '../../../../../fields/Doc'; import * as React from 'react'; -import * as d3 from 'd3'; -import { IReactionDisposer, action, computed, observable, reaction } from 'mobx'; -import { LinkManager } from '../../../../util/LinkManager'; -import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; -import { PinProps, PresBox } from '../../trails'; -import { Docs } from '../../../../documents/Documents'; -import { List } from '../../../../../fields/List'; -import './Chart.scss'; -import { ColorPicker, EditableText, IconButton, Size, Type } from 'browndash-components'; import { FaFillDrip } from 'react-icons/fa'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Doc, StrListCast } from '../../../../../fields/Doc'; +import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; +import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; +import { Docs } from '../../../../documents/Documents'; +import { LinkManager } from '../../../../util/LinkManager'; +import { undoable } from '../../../../util/UndoManager'; +import { PinProps, PresBox } from '../../trails'; import { scaleCreatorNumerical, yAxisCreator } from '../utils/D3Utils'; -import { undoBatch, undoable } from '../../../../util/UndoManager'; +import './Chart.scss'; export interface HistogramProps { rootDoc: Doc; layoutDoc: Doc; axes: string[]; - pairs: { [key: string]: any }[]; + records: { [key: string]: any }[]; width: number; height: number; dataDoc: Doc; @@ -48,41 +48,41 @@ export class Histogram extends React.Component { // filters all data to just display selected data if brushed (created from an incoming link) @computed get _histogramData() { - var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); + var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds); if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; - if (/\d/.test(this.props.pairs[0][ax0])) { + if (/\d/.test(this.props.records[0][ax0])) { this.numericalXData = true; } - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: pair[this.props.axes[0]] })); + return this.props.records + ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)]))) + .map(record => ({ [ax0]: record[this.props.axes[0]] })); } var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; - if (/\d/.test(this.props.pairs[0][ax0])) { + if (/\d/.test(this.props.records[0][ax0])) { this.numericalXData = true; } - if (/\d/.test(this.props.pairs[0][ax1])) { + if (/\d/.test(this.props.records[0][ax1])) { this.numericalYData = true; } - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] })); + return this.props.records + ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)]))) + .map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] })); } @computed get defaultGraphTitle() { var ax0 = this.props.axes[0]; var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined; - if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.pairs[0][ax1]) || !this.numericalYData) { + if (this.props.axes.length < 2 || !ax1 || !/\d/.test(this.props.records[0][ax1]) || !this.numericalYData) { return ax0 + ' Histogram'; } else return ax0 + ' by ' + ax1 + ' Histogram'; } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // get links where this chart doc is the target of the link + .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @@ -100,11 +100,7 @@ export class Histogram extends React.Component { componentDidMount = () => { this._disposers.chartData = reaction( () => ({ dataSet: this._histogramData, w: this.width, h: this.height }), - ({ dataSet, w, h }) => { - if (dataSet!.length > 0) { - this.drawChart(dataSet, w, h); - } - }, + ({ dataSet, w, h }) => dataSet!.length > 0 && this.drawChart(dataSet, w, h), { fireImmediately: true } ); }; @@ -114,7 +110,6 @@ export class Histogram extends React.Component { // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) getAnchor = (pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ - // title: 'histogram doc selection' + this._currSelected, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.rootDoc); @@ -131,21 +126,15 @@ export class Histogram extends React.Component { // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { - var validData = dataSet.filter((d: { [x: string]: unknown }) => { - var valid = true; - Object.keys(dataSet[0]).map(key => { - if (!d[key] || Number.isNaN(d[key])) valid = false; - }); - return valid; - }); - var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; - const data = validData.map((d: { [x: string]: any }) => { - if (this.numericalXData) { - return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\ !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); + const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; + return !field + ? [] + : validData.map((d: { [x: string]: any }) => + !this.numericalXData // + ? d[field] + : +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { d3.select(this._histogramRef.current).select('svg').remove(); d3.select(this._histogramRef.current).select('.tooltip').remove(); - var data = this.data(dataSet); - var xAxisTitle = Object.keys(dataSet[0])[0]; - var yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency'; - let uniqueArr: unknown[] = [...new Set(data)]; + const data = this.data(dataSet); + const xAxisTitle = Object.keys(dataSet[0])[0]; + const yAxisTitle = this.numericalYData ? Object.keys(dataSet[0])[1] : 'frequency'; + const uniqueArr: unknown[] = [...new Set(data)]; var numBins = this.numericalXData && Number.isInteger(data[0]) ? this.rangeVals.xMax! - this.rangeVals.xMin! : uniqueArr.length; var translateXAxis = !this.numericalXData || numBins < this.maxBins ? width / (numBins + 1) / 2 : 0; if (numBins > this.maxBins) numBins = this.maxBins; - var startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0; - var endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins; + const startingPoint = this.numericalXData ? this.rangeVals.xMin! : 0; + const endingPoint = this.numericalXData ? this.rangeVals.xMax! : numBins; // converts data into Objects - var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => { - var valid = true; - Object.keys(dataSet[0]).map(key => { - if (!d[key] || Number.isNaN(d[key])) valid = false; - }); - return valid; - }); + var histDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); if (!this.numericalXData) { var histStringDataSet: { [x: string]: unknown }[] = []; if (this.numericalYData) { @@ -321,7 +304,7 @@ export class Histogram extends React.Component { // click/hover const onPointClick = action((e: any) => this.highlightSelectedBar(true, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet)); const onHover = action((e: any) => { - const selected = this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet); + this.highlightSelectedBar(false, svg, eachRectWidth, d3.pointer(e)[0], xAxisTitle, yAxisTitle, histDataSet); updateHighlights(); }); const mouseOut = action((e: any) => { @@ -360,10 +343,10 @@ export class Histogram extends React.Component { 'transform', this.numericalYData ? function (d) { - var eachData = histDataSet.filter((data: { [x: string]: number }) => { + const eachData = histDataSet.filter((data: { [x: string]: number }) => { return data[xAxisTitle] == d[0]; }); - var length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { 'height', this.numericalYData ? function (d) { - var eachData = histDataSet.filter((data: { [x: string]: number }) => { + const eachData = histDataSet.filter((data: { [x: string]: number }) => { return data[xAxisTitle] == d[0]; }); - var length = eachData.length ? eachData[0][yAxisTitle].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { 'class', selected ? function (d) { - return selected && selected[0] == d[0] ? 'histogram-bar hover' : 'histogram-bar'; + return selected && selected[0] === d[0] ? 'histogram-bar hover' : 'histogram-bar'; } : function (d) { return 'histogram-bar'; @@ -397,11 +380,11 @@ export class Histogram extends React.Component { ) .attr('fill', d => { var barColor; - var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::')); - barColors.map(each => { + const barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::')); + barColors.forEach(each => { if (d[0] && d[0].toString() && each[0] == d[0].toString()) barColor = each[1]; else { - var range = StrCast(each[0]).split(' to '); + const range = StrCast(each[0]).split(' to '); if (Number(range[0]) <= d[0] && d[0] <= Number(range[1])) barColor = each[1]; } }); @@ -411,23 +394,19 @@ export class Histogram extends React.Component { @action changeSelectedColor = (color: string) => { this.curBarSelected.attr('fill', color); - var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { - if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1); - }); + barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1)); barColors.push(StrCast(barName + '::' + color)); }; @action eraseSelectedColor = () => { this.curBarSelected.attr('fill', this.props.layoutDoc.defaultHistogramColor); - var barName = StrCast(this._currSelected[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { - if (each.split('::')[0] == barName) barColors.splice(barColors.indexOf(each), 1); - }); + barColors.forEach(each => each.split('::')[0] === barName && barColors.splice(barColors.indexOf(each), 1)); }; render() { @@ -439,25 +418,22 @@ export class Histogram extends React.Component { if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; if (!this.props.layoutDoc.defaultHistogramColor) this.props.layoutDoc.defaultHistogramColor = '#69b3a2'; if (!this.props.layoutDoc.histogramBarColors) this.props.layoutDoc.histogramBarColors = new List(); - var selected: string; + var selected = 'none'; if (this._currSelected) { curSelectedBarName = StrCast(this._currSelected![this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { - key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : ''; - }); - selected = selected.substring(0, selected.length - 2); - selected += ' }'; - } else selected = 'none'; + Object.keys(this._currSelected).forEach(key => + key // + ? (selected += key + ': ' + this._currSelected[key] + ', ') + : '' + ); + selected = selected.substring(0, selected.length - 2) + ' }'; + } var selectedBarColor; var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::')); - barColors.map(each => { - if (each[0] == curSelectedBarName!) selectedBarColor = each[1]; - }); + barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1])); - this.componentDidMount(); - - if (this._histogramData.length > 0 || (!this.incomingLinks || this.incomingLinks.length==0)) { + if (this._histogramData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) { return this.props.axes.length >= 1 ? (
@@ -514,10 +490,8 @@ export class Histogram extends React.Component { ) : ( {'first use table view to select a column to graph'} ); - } else - return ( - // when it is a brushed table and the incoming table doesn't have any rows selected -
Selected rows of data from the incoming DataVizBox to display.
- ); + } + // when it is a brushed table and the incoming table doesn't have any rows selected + return
Selected rows of data from the incoming DataVizBox to display.
; } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 46cf27705..655c6de20 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -28,7 +28,7 @@ export interface LineChartProps { rootDoc: Doc; layoutDoc: Doc; axes: string[]; - pairs: { [key: string]: any }[]; + records: { [key: string]: any }[]; width: number; height: number; dataDoc: Doc; @@ -50,11 +50,11 @@ export class LineChart extends React.Component { // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates @computed get _lineChartData() { - var guids = StrListCast(this.props.layoutDoc.dataViz_rowGuids); + var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds); if (this.props.axes.length <= 1) return []; - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ x: Number(pair[this.props.axes[0]]), y: Number(pair[this.props.axes[1]]) })) + return this.props.records + ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)]))) + .map(record => ({ x: Number(record[this.props.axes[0]]), y: Number(record[this.props.axes[1]]) })) .sort((a, b) => (a.x < b.x ? -1 : 1)); } @computed get graphTitle() { @@ -63,7 +63,7 @@ export class LineChart extends React.Component { @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links .filter(link => { - return link.link_anchor_1 == this.props.rootDoc.draggedFrom; + return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz; }) // get links where this chart doc is the target of the link .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @@ -73,7 +73,7 @@ export class LineChart extends React.Component { return this.incomingLinks // all links that are pointing to this node .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes .filter(dvb => dvb) - .map(dvb => dvb.pairs?.filter(pair => pair['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor + .map(dvb => dvb.records?.filter(record => record['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor .lastElement(); } @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { @@ -91,7 +91,7 @@ export class LineChart extends React.Component { // redraw annotations when the chart data has changed, or the local or inherited selection has changed this.clearAnnotations(); this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true); - this.incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); + this.incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]]))); } }, { fireImmediately: true } @@ -117,7 +117,7 @@ export class LineChart extends React.Component { // redraw annotations when the chart data has changed, or the local or inherited selection has changed this.clearAnnotations(); selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true); - incomingSelected?.forEach((pair: any) => this.drawAnnotations(Number(pair[this.props.axes[0]]), Number(pair[this.props.axes[1]]))); + incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]]))); }, { fireImmediately: true } ); @@ -193,7 +193,7 @@ export class LineChart extends React.Component { @computed get defaultGraphTitle() { var ax0 = this.props.axes[0]; var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined; - if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) { + if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) { return ax0 + ' Line Chart'; } else return ax1 + ' by ' + ax0 + ' Line Chart'; } @@ -217,7 +217,7 @@ export class LineChart extends React.Component { // TODO: nda - get rid of svg element in the list? if (this._currSelected && this._currSelected.x == x && this._currSelected.y == y) this._currSelected = undefined; else this._currSelected = x !== undefined && y !== undefined ? { x, y } : undefined; - this.props.pairs.forEach(pair => pair[this.props.axes[0]] === x && pair[this.props.axes[1]] === y && (pair.selected = true)); + this.props.records.forEach(record => record[this.props.axes[0]] === x && record[this.props.axes[1]] === y && (record.selected = true)); } drawDataPoints(data: DataPoint[], idx: number, xScale: d3.ScaleLinear, yScale: d3.ScaleLinear) { @@ -358,10 +358,10 @@ export class LineChart extends React.Component { else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_lineChart_' + this.props.axes[0]; if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; const selectedPt = this._currSelected ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none'; - if (this._lineChartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){ - return this.props.axes.length>=2 && /\d/.test(this.props.pairs[0][this.props.axes[0]]) && /\d/.test(this.props.pairs[0][this.props.axes[1]]) ? ( -
-
+ if (this._lineChartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) { + return this.props.axes.length >= 2 && /\d/.test(this.props.records[0][this.props.axes[0]]) && /\d/.test(this.props.records[0][this.props.axes[1]]) ? ( +
+
{ private _disposers: { [key: string]: IReactionDisposer } = {}; private _piechartRef: React.RefObject = React.createRef(); private _piechartSvg: d3.Selection | undefined; - private byCategory: boolean = true; // whether the data is organized by category or by specified number percentages/ratios - @observable _currSelected: any | undefined = undefined; // Object of selected slice private curSliceSelected: any = undefined; // d3 data of selected slice private selectedData: any = undefined; // Selection of selected slice private hoverOverData: any = undefined; // Selection of slice being hovered over + @observable _currSelected: any | undefined = undefined; // Object of selected slice - @computed get rowGuids() { - return StrListCast(this.props.layoutDoc.dataViz_rowGuids) + @computed get _tableDataIds() { + return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows); + } + // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent) + @computed get _tableData() { + return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); + } + + // organized by specified number percentages/ratios if one column is selected and it contains numbers + // otherwise, assume data is organized by categories + @computed get byCategory() { + if (this.props.axes.length === 1) { + return !/\d/.test(this.props.records[0][this.props.axes[0]]); + } + return true; } // filters all data to just display selected data if brushed (created from an incoming link) @computed get _piechartData() { if (this.props.axes.length < 1) return []; + + const ax0 = this.props.axes[0]; if (this.props.axes.length < 2) { - var ax0 = this.props.axes[0]; - if (/\d/.test(this.props.pairs[0][ax0])) { - this.byCategory = false; - } - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: pair[this.props.axes[0]] })); - } - var ax0 = this.props.axes[0]; - var ax1 = this.props.axes[1]; - if (/\d/.test(this.props.pairs[0][ax0])) { - this.byCategory = false; + return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] })); } - return this.props.pairs - ?.filter(pair => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)]))) - .map(pair => ({ [ax0]: pair[this.props.axes[0]], [ax1]: pair[this.props.axes[1]] })); + const ax1 = this.props.axes[1]; + return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] })); } @computed get defaultGraphTitle() { var ax0 = this.props.axes[0]; var ax1 = this.props.axes.length > 1 ? this.props.axes[1] : undefined; - if (this.props.axes.length < 2 || !/\d/.test(this.props.pairs[0][ax0]) || !ax1) { + if (this.props.axes.length < 2 || !/\d/.test(this.props.records[0][ax0]) || !ax1) { return ax0 + ' Pie Chart'; - } else return ax1 + ' by ' + ax0 + ' Pie Chart'; + } + return ax1 + ' by ' + ax0 + ' Pie Chart'; } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => link.link_anchor_1 == this.props.rootDoc.draggedFrom) // get links where this chart doc is the target of the link + .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @@ -119,21 +121,15 @@ export class PieChart extends React.Component { // cleans data by converting numerical data to numbers and taking out empty cells data = (dataSet: any) => { - var validData = dataSet.filter((d: { [x: string]: unknown }) => { - var valid = true; - Object.keys(dataSet[0]).map(key => { - if (!d[key] || Number.isNaN(d[key])) valid = false; - }); - return valid; - }); - var field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; - const data = validData.map((d: { [x: string]: any }) => { - if (!this.byCategory) { - return +d[field!].replace(/\$/g, '').replace(/\%/g, '').replace(/\ !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); + const field = dataSet[0] ? Object.keys(dataSet[0])[0] : undefined; + return !field + ? undefined + : validData.map((d: { [x: string]: any }) => + this.byCategory + ? d[field] // + : +d[field].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { // converts data into Objects var data = this.data(dataSet); - var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => { - var valid = true; - Object.keys(dataSet[0]).map(key => { - if (!d[key] || Number.isNaN(d[key])) valid = false; - }); - return valid; - }); + var pieDataSet = dataSet.filter((d: { [x: string]: unknown }) => !Object.keys(dataSet[0]).some(key => !d[key] || Number.isNaN(d[key]))); if (this.byCategory) { let uniqueCategories = [...new Set(data)]; var pieStringDataSet: { frequency: number }[] = []; @@ -235,7 +225,7 @@ export class PieChart extends React.Component { // click/hover const onPointClick = action((e: any) => this.highlightSelectedSlice(true, svg, arc, radius, d3.pointer(e), pieDataSet)); const onHover = action((e: any) => { - const selected = this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet); + this.highlightSelectedSlice(false, svg, arc, radius, d3.pointer(e), pieDataSet); updateHighlights(); }); const mouseOut = action((e: any) => { @@ -255,16 +245,19 @@ export class PieChart extends React.Component { // drawing the slices var selected = this.selectedData; var arcs = g.selectAll('arc').data(pie(data)).enter().append('g'); + const sliceColors = StrListCast(this.props.layoutDoc.pieSliceColors).map(each => each.split('::')); + const possibleDataPointVals = pieDataSet.map((each: { [x: string]: any | { valueOf(): number } }) => { + try { + each[percentField] = Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\ { - var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => { - try { - return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\ pval[percentField] === Number(d.data)); if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; else { dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; @@ -272,11 +265,8 @@ export class PieChart extends React.Component { } var sliceColor; if (dataPoint) { - var accessByName = dataPoint[this.props.axes[0]].replace(/\$/g, '').replace(/\%/g, '').replace(/\ each.split('::')); - sliceColors.map(each => { - if (each[0] == StrCast(accessByName)) sliceColor = each[1]; - }); + var accessByName = dataPoint[this.props.axes[0]]; + sliceColors.forEach(each => each[0] == StrCast(accessByName) && (sliceColor = each[1])); } return sliceColor ? StrCast(sliceColor) : d3.schemeSet3[i] ? d3.schemeSet3[i] : d3.schemeSet3[i % d3.schemeSet3.length]; }) @@ -298,29 +288,25 @@ export class PieChart extends React.Component { // adding labels trackDuplicates = {}; data.forEach((eachData: any) => (!trackDuplicates[eachData] ? (trackDuplicates[eachData] = 0) : null)); - arcs.append('text') - .attr('transform', function (d) { - var centroid = arc.centroid(d as unknown as d3.DefaultArcObject); - var heightOffset = (centroid[1] / radius) * Math.abs(centroid[1]); - return 'translate(' + (centroid[0] + centroid[0] / (radius * 0.02)) + ',' + (centroid[1] + heightOffset) + ')'; - }) - .attr('text-anchor', 'middle') - .text(function (d) { - var possibleDataPoints = pieDataSet.filter((each: { [x: string]: any | { valueOf(): number } }) => { - try { - return Number(each[percentField].replace(/\$/g, '').replace(/\%/g, '').replace(/\ pval[percentField] === Number(d.data)); + if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; + else { + dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; + trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; } + return dataPoint ? dataPoint[percentField]! + (!descriptionField ? '' : ' - ' + dataPoint[descriptionField])! : ''; }); - var dataPoint; - if (possibleDataPoints.length == 1) dataPoint = possibleDataPoints[0]; - else { - dataPoint = possibleDataPoints[trackDuplicates[d.data.toString()]]; - trackDuplicates[d.data.toString()] = trackDuplicates[d.data.toString()] + 1; - } - return dataPoint ? dataPoint[percentField]! + (!descriptionField ? '' : ' - ' + dataPoint[descriptionField])! : ''; - }); }; @action changeSelectedColor = (color: string) => { @@ -335,7 +321,6 @@ export class PieChart extends React.Component { }; render() { - this.componentDidMount(); var titleAccessor: any = ''; if (this.props.axes.length == 2) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0] + '-' + this.props.axes[1]; else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_pieChart_' + this.props.axes[0]; @@ -358,7 +343,7 @@ export class PieChart extends React.Component { if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1]; }); - if (this._piechartData.length>0 || (!this.incomingLinks || this.incomingLinks.length==0)){ + if (this._piechartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) { return this.props.axes.length >= 1 ? (
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index e1c5aa125..e90b541ae 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,21 +1,21 @@ -import { action, computed } from 'mobx'; +import { action, computed, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, Field, StrListCast } from '../../../../../fields/Doc'; +import { Doc, Field, NumListCast, StrListCast } from '../../../../../fields/Doc'; import { List } from '../../../../../fields/List'; +import { listSpec } from '../../../../../fields/Schema'; +import { Cast, DocCast } from '../../../../../fields/Types'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils'; import { DragManager } from '../../../../util/DragManager'; +import { LinkManager } from '../../../../util/LinkManager'; import { DocumentView } from '../../DocumentView'; import { DataVizView } from '../DataVizBox'; -import { LinkManager } from '../../../../util/LinkManager'; -import { Cast, DocCast } from '../../../../../fields/Types'; import './Chart.scss'; -import { listSpec } from '../../../../../fields/Schema'; interface TableBoxProps { rootDoc: Doc; layoutDoc: Doc; - pairs: { [key: string]: any }[]; + records: { [key: string]: any }[]; selectAxes: (axes: string[]) => void; axes: string[]; width: number; @@ -31,20 +31,26 @@ interface TableBoxProps { @observer export class TableBox extends React.Component { - // filters all data to just display selected data if brushed (created from an incoming link) - @computed get _tableData() { - if (this.incomingLinks.length <= 0) return this.props.pairs; - return this.props.pairs?.filter(pair => StrListCast(this.incomingLinks[0]?.dataViz_selectedRows).includes(this.rowGuids[this.props.pairs.indexOf(pair)])); + _inputChangedDisposer?: IReactionDisposer; + componentDidMount() { + // if the tableData changes (ie., when records are selected by the parent (input) visulization), + // then we need to remove any selected rows that are no longer part of the visualized dataset. + this._inputChangedDisposer = reaction(() => this._tableData.slice(), this.filterSelectedRowsDown, { fireImmediately: true }); + } + componentWillUnmount() { + this._inputChangedDisposer?.(); + } + @computed get _tableDataIds() { + return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows); } - @computed get rowGuids() { - return StrListCast(this.props.layoutDoc.dataViz_rowGuids) + // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent) + @computed get _tableData() { + return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); } @computed get incomingLinks() { return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => { - return link.link_anchor_1 == this.props.rootDoc.draggedFrom; - }) // get links where this chart doc is the target of the link + .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @@ -53,128 +59,115 @@ export class TableBox extends React.Component { } // updates the 'selected' field to no longer include rows that aren't in the table - filterSelectedRowsDown() { - if (!this.props.layoutDoc.dataViz_selectedRows) this.props.layoutDoc.dataViz_selectedRows = new List(); - const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null); - const incomingSelected = this.incomingLinks.length ? StrListCast(this.incomingLinks[0].dataViz_selectedRows) : undefined; - if (incomingSelected) { - const newsel = selected.filter(guid => incomingSelected.includes(guid));// filters through selected to remove guids that were removed in the incoming data - selected.length = 0; - selected.push(...newsel); - } - } + filterSelectedRowsDown = () => { + const selected = NumListCast(this.props.layoutDoc.dataViz_selectedRows); + this.props.layoutDoc.dataViz_selectedRows = new List(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data + }; render() { - this.filterSelectedRowsDown(); if (this._tableData.length > 0) { return ( -
+
+ r?.addEventListener( + 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) + (e: WheelEvent) => { + if (!r.scrollTop && e.deltaY <= 0) e.preventDefault(); + e.stopPropagation(); + }, + { passive: false } + ) + }> - {this.columns - .filter(col => !col.startsWith('select')) - .map(col => { - const header = React.createRef(); - return ( - - ); - })} + {this.columns.map(col => ( + + ))} - {this._tableData?.map((p, i) => { - var containsData = false; - var guid = this.rowGuids[this.props.pairs.indexOf(p)]; - this.columns.map(col => { - if (p[col] != '' && p[col] != null && p[col] != undefined) containsData = true; - }); - if (containsData) { - return ( - { - // selecting a row - const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('string'), null); - if (selected.includes(guid)) selected.splice(selected.indexOf(guid), 1); - else { - selected.push(guid); - } - })} - style={{ background: StrListCast(this.props.layoutDoc.dataViz_selectedRows).includes(guid) ? 'lightgrey' : '', width: '110%' }}> - {this.columns.map(col => { - // each cell - var colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false; - return ( - - ); - })} - - ); - } - })} + {this._tableDataIds + ?.map(rowId => ({ record: this.props.records[rowId], rowId })) + .map(({ record, rowId }) => ( + { + // selecting a row + const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null); + if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1); + else selected?.push(rowId); + e.stopPropagation(); + })} + style={{ background: NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '', width: '110%' }}> + {this.columns.map(col => { + // each cell + const colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false; + return ( + + ); + })} + + ))}
{ - const downX = e.clientX; - const downY = e.clientY; - setupMoveUpEvents( - {}, - e, - e => { - // dragging off a column to create a brushed DataVizBox - const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!; - const targetCreator = (annotationOn: Doc | undefined) => { - const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!); - embedding._dataViz = DataVizView.TABLE; - embedding._dataViz_axes = new List([col, col]); - embedding._draggedFrom = this.props.docView?.()!.rootDoc!; - embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; - embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors); - embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor; - embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors); - return embedding; - }; - if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { - DragManager.StartAnchorAnnoDrag([header.current!], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, { - dragComplete: e => { - if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { - e.linkDocument.link_displayLine = true; - e.linkDocument.link_matchEmbeddings = true; - // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; - // e.annoDragData.linkSourceDoc.followLinkZoom = false; - } - }, - }); - return true; - } - return false; - }, - emptyFunction, - action(e => { - const newAxes = this.props.axes; - if (newAxes.includes(col)) { - newAxes.splice(newAxes.indexOf(col), 1); - } else if (newAxes.length > 1) { - newAxes[1] = col; - } else { - newAxes.push(col); - } - this.props.selectAxes(newAxes); - }) - ); - }}> - {col} - { + const downX = e.clientX; + const downY = e.clientY; + setupMoveUpEvents( + {}, + e, + e => { + // dragging off a column to create a brushed DataVizBox + const sourceAnchorCreator = () => this.props.docView?.()!.rootDoc!; + const targetCreator = (annotationOn: Doc | undefined) => { + const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!); + embedding._dataViz = DataVizView.TABLE; + embedding._dataViz_axes = new List([col, col]); + embedding._dataViz_parentViz = this.props.docView?.()!.rootDoc!; + embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; + embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors); + embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor; + embedding.pieSliceColors = Field.Copy(this.props.layoutDoc.pieSliceColors); + return embedding; + }; + if (this.props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) { + DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, { + dragComplete: e => { + if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { + e.linkDocument.link_displayLine = true; + e.linkDocument.link_matchEmbeddings = true; + // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; + // e.annoDragData.linkSourceDoc.followLinkZoom = false; + } + }, + }); + return true; + } + return false; + }, + emptyFunction, + action(e => { + const newAxes = this.props.axes; + if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1); + else if (newAxes.length > 1) newAxes[1] = col; + else newAxes.push(col); + this.props.selectAxes(newAxes); + }) + ); + }}> + {col} +
- {p[col]} -
+ {record[col]} +
-- cgit v1.2.3-70-g09d2 From 6f2fc57f29a90b02dad0bc98c20ec69ef90c39ef Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 25 Aug 2023 11:58:14 -0400 Subject: simplification of vizBoxes to use parentViz instead of going through links --- .../nodes/DataVizBox/components/Histogram.tsx | 29 +++++++++-------- .../nodes/DataVizBox/components/LineChart.tsx | 36 ++++++++++++---------- .../views/nodes/DataVizBox/components/PieChart.tsx | 15 ++++----- .../views/nodes/DataVizBox/components/TableBox.tsx | 15 ++++----- 4 files changed, 51 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index 48cbe5c5f..50facf03e 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -5,7 +5,7 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx' import { observer } from 'mobx-react'; import * as React from 'react'; import { FaFillDrip } from 'react-icons/fa'; -import { Doc, StrListCast } from '../../../../../fields/Doc'; +import { Doc, NumListCast, StrListCast } from '../../../../../fields/Doc'; import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; @@ -46,18 +46,22 @@ export class Histogram extends React.Component { private selectedData: any = undefined; // Selection of selected bar private hoverOverData: any = undefined; // Selection of bar being hovered over + @computed get _tableDataIds() { + return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows); + } + // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent) + @computed get _tableData() { + return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); + } // filters all data to just display selected data if brushed (created from an incoming link) @computed get _histogramData() { - var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds); if (this.props.axes.length < 1) return []; if (this.props.axes.length < 2) { var ax0 = this.props.axes[0]; if (/\d/.test(this.props.records[0][ax0])) { this.numericalXData = true; } - return this.props.records - ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)]))) - .map(record => ({ [ax0]: record[this.props.axes[0]] })); + return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]] })); } var ax0 = this.props.axes[0]; var ax1 = this.props.axes[1]; @@ -67,9 +71,7 @@ export class Histogram extends React.Component { if (/\d/.test(this.props.records[0][ax1])) { this.numericalYData = true; } - return this.props.records - ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)]))) - .map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] })); + return this._tableData.map(record => ({ [ax0]: record[this.props.axes[0]], [ax1]: record[this.props.axes[1]] })); } @computed get defaultGraphTitle() { @@ -80,10 +82,11 @@ export class Histogram extends React.Component { } else return ax0 + ' by ' + ax1 + ' Histogram'; } - @computed get incomingLinks() { - return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link - .map(link => DocCast(link.link_anchor_1)); // then return the source of the link + @computed get parentViz() { + return DocCast(this.props.rootDoc.dataViz_parentViz); + // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links + // .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link + // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { @@ -433,7 +436,7 @@ export class Histogram extends React.Component { var barColors = StrListCast(this.props.layoutDoc.histogramBarColors).map(each => each.split('::')); barColors.forEach(each => each[0] === curSelectedBarName && (selectedBarColor = each[1])); - if (this._histogramData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) { + if (this._histogramData.length > 0 || !this.parentViz) { return this.props.axes.length >= 1 ? (
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 655c6de20..3ef2bd8b0 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx' import { observer } from 'mobx-react'; import * as React from 'react'; import * as d3 from 'd3'; -import { Doc, DocListCast, StrListCast } from '../../../../../fields/Doc'; +import { Doc, DocListCast, NumListCast, StrListCast } from '../../../../../fields/Doc'; import { Id } from '../../../../../fields/FieldSymbols'; import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; @@ -49,32 +49,34 @@ export class LineChart extends React.Component { @observable _currSelected: SelectedDataPoint | undefined = undefined; // TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates + @computed get _tableDataIds() { + return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows); + } + // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent) + @computed get _tableData() { + return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); + } @computed get _lineChartData() { var guids = StrListCast(this.props.layoutDoc.dataViz_rowIds); if (this.props.axes.length <= 1) return []; - return this.props.records - ?.filter(record => (!this.incomingLinks.length ? true : this.incomingLinks[0]!.dataViz_selectedRows && StrListCast(this.incomingLinks[0].dataViz_selectedRows).includes(guids[this.props.records.indexOf(record)]))) - .map(record => ({ x: Number(record[this.props.axes[0]]), y: Number(record[this.props.axes[1]]) })) - .sort((a, b) => (a.x < b.x ? -1 : 1)); + return this._tableData.map(record => ({ x: Number(record[this.props.axes[0]]), y: Number(record[this.props.axes[1]]) })).sort((a, b) => (a.x < b.x ? -1 : 1)); } @computed get graphTitle() { return this.props.axes[1] + ' vs. ' + this.props.axes[0] + ' Line Chart'; } - @computed get incomingLinks() { - return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => { - return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz; - }) // get links where this chart doc is the target of the link - .map(link => DocCast(link.link_anchor_1)); // then return the source of the link + @computed get parentViz() { + return DocCast(this.props.rootDoc.dataViz_parentViz); + // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links + // .filter(link => { + // return link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz; + // }) // get links where this chart doc is the target of the link + // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @computed get incomingSelected() { // return selected x and y axes // otherwise, use the selection of whatever is linked to us - return this.incomingLinks // all links that are pointing to this node - .map(anchor => DocumentManager.Instance.getFirstDocumentView(anchor)?.ComponentView as DataVizBox) // get their data viz boxes - .filter(dvb => dvb) - .map(dvb => dvb.records?.filter(record => record['select' + dvb.rootDoc[Id]])) // get all the datapoints they have selected field set by incoming anchor - .lastElement(); + const incomingVizBox = DocumentManager.Instance.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox; + return incomingVizBox.records?.filter(record => record['select' + incomingVizBox.rootDoc[Id]]); // get all the datapoints they have selected field set by incoming anchor } @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { return minMaxRange([this._lineChartData]); @@ -358,7 +360,7 @@ export class LineChart extends React.Component { else if (this.props.axes.length > 0) titleAccessor = 'dataViz_title_lineChart_' + this.props.axes[0]; if (!this.props.layoutDoc[titleAccessor]) this.props.layoutDoc[titleAccessor] = this.defaultGraphTitle; const selectedPt = this._currSelected ? `{ ${this.props.axes[0]}: ${this._currSelected.x} ${this.props.axes[1]}: ${this._currSelected.y} }` : 'none'; - if (this._lineChartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) { + if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) { return this.props.axes.length >= 2 && /\d/.test(this.props.records[0][this.props.axes[0]]) && /\d/.test(this.props.records[0][this.props.axes[1]]) ? (
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 79d7b3f23..a8aa51897 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -42,11 +42,11 @@ export class PieChart extends React.Component { @observable _currSelected: any | undefined = undefined; // Object of selected slice @computed get _tableDataIds() { - return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows); + return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows); } // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent) @computed get _tableData() { - return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); + return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); } // organized by specified number percentages/ratios if one column is selected and it contains numbers @@ -78,10 +78,11 @@ export class PieChart extends React.Component { return ax1 + ' by ' + ax0 + ' Pie Chart'; } - @computed get incomingLinks() { - return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link - .map(link => DocCast(link.link_anchor_1)); // then return the source of the link + @computed get parentViz() { + return DocCast(this.props.rootDoc.dataViz_parentViz); + // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links + // .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link + // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } componentWillUnmount() { @@ -343,7 +344,7 @@ export class PieChart extends React.Component { if (each[0] == curSelectedSliceName!) selectedSliceColor = each[1]; }); - if (this._piechartData.length > 0 || !this.incomingLinks || this.incomingLinks.length == 0) { + if (this._piechartData.length > 0 || !this.parentViz) { return this.props.axes.length >= 1 ? (
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index e90b541ae..7bca08c15 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -41,17 +41,18 @@ export class TableBox extends React.Component { this._inputChangedDisposer?.(); } @computed get _tableDataIds() { - return !this.incomingLinks.length ? this.props.records.map((rec, i) => i) : NumListCast(this.incomingLinks[0].dataViz_selectedRows); + return !this.parentViz ? this.props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows); } // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent) @computed get _tableData() { - return !this.incomingLinks.length ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); + return !this.parentViz ? this.props.records : this._tableDataIds.map(rowId => this.props.records[rowId]); } - @computed get incomingLinks() { - return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links - .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link - .map(link => DocCast(link.link_anchor_1)); // then return the source of the link + @computed get parentViz() { + return DocCast(this.props.rootDoc.dataViz_parentViz); + // return LinkManager.Instance.getAllRelatedLinks(this.props.rootDoc) // out of all links + // .filter(link => link.link_anchor_1 == this.props.rootDoc.dataViz_parentViz) // get links where this chart doc is the target of the link + // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } @computed get columns() { @@ -105,7 +106,7 @@ export class TableBox extends React.Component { const embedding = Doc.MakeEmbedding(this.props.docView?.()!.rootDoc!); embedding._dataViz = DataVizView.TABLE; embedding._dataViz_axes = new List([col, col]); - embedding._dataViz_parentViz = this.props.docView?.()!.rootDoc!; + embedding._dataViz_parentViz = this.props.rootDoc; embedding.annotationOn = annotationOn; //this.props.docView?.()!.rootDoc!; embedding.histogramBarColors = Field.Copy(this.props.layoutDoc.histogramBarColors); embedding.defaultHistogramColor = this.props.layoutDoc.defaultHistogramColor; -- cgit v1.2.3-70-g09d2 From 25d1448fa5d840e2e9475f088748f62d6aed0248 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 25 Aug 2023 12:25:45 -0400 Subject: added cmdKey clicking on table rows to 'highlight' them by setting dataViz_highlitedRows, and changed lineChart's to read dataViz_highlitedRows to highlight data points --- .../views/nodes/DataVizBox/components/LineChart.tsx | 13 +++++++------ .../views/nodes/DataVizBox/components/TableBox.tsx | 21 ++++++++++++++++----- 2 files changed, 23 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 3ef2bd8b0..6c9922c0a 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -72,11 +72,12 @@ export class LineChart extends React.Component { // }) // get links where this chart doc is the target of the link // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } - @computed get incomingSelected() { + @computed get incomingHighlited() { // return selected x and y axes // otherwise, use the selection of whatever is linked to us const incomingVizBox = DocumentManager.Instance.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox; - return incomingVizBox.records?.filter(record => record['select' + incomingVizBox.rootDoc[Id]]); // get all the datapoints they have selected field set by incoming anchor + const highlitedRowIds = NumListCast(incomingVizBox.rootDoc.dataViz_highlitedRows); + return this._tableData.filter((record, i) => highlitedRowIds.includes(this._tableDataIds[i])); // get all the datapoints they have selected field set by incoming anchor } @computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } { return minMaxRange([this._lineChartData]); @@ -93,7 +94,7 @@ export class LineChart extends React.Component { // redraw annotations when the chart data has changed, or the local or inherited selection has changed this.clearAnnotations(); this._currSelected && this.drawAnnotations(Number(this._currSelected.x), Number(this._currSelected.y), true); - this.incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]]))); + this.incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]]))); } }, { fireImmediately: true } @@ -113,13 +114,13 @@ export class LineChart extends React.Component { this._disposers.highlights = reaction( () => ({ selected: this._currSelected, - incomingSelected: this.incomingSelected, + incomingHighlited: this.incomingHighlited, }), - ({ selected, incomingSelected }) => { + ({ selected, incomingHighlited }) => { // redraw annotations when the chart data has changed, or the local or inherited selection has changed this.clearAnnotations(); selected && this.drawAnnotations(Number(selected.x), Number(selected.y), true); - incomingSelected?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]]))); + incomingHighlited?.forEach((record: any) => this.drawAnnotations(Number(record[this.props.axes[0]]), Number(record[this.props.axes[1]]))); }, { fireImmediately: true } ); diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 7bca08c15..9e2ce1c80 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -59,10 +59,12 @@ export class TableBox extends React.Component { return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header != '' && header != undefined) : []; } - // updates the 'selected' field to no longer include rows that aren't in the table + // updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table filterSelectedRowsDown = () => { const selected = NumListCast(this.props.layoutDoc.dataViz_selectedRows); this.props.layoutDoc.dataViz_selectedRows = new List(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data + const highlighted = NumListCast(this.props.layoutDoc.dataViz_highlitedRows); + this.props.layoutDoc.dataViz_highlitedRows = new List(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data }; render() { @@ -152,12 +154,21 @@ export class TableBox extends React.Component { className="table-row" onClick={action(e => { // selecting a row - const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null); - if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1); - else selected?.push(rowId); + if (e.metaKey) { + const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null); + if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1); + else highlited?.push(rowId); + } else { + const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null); + if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1); + else selected?.push(rowId); + } e.stopPropagation(); })} - style={{ background: NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '', width: '110%' }}> + style={{ + background: NumListCast(this.props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this.props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '', + width: '110%', + }}> {this.columns.map(col => { // each cell const colSelected = this.props.axes.length > 1 ? this.props.axes[0] == col || this.props.axes[1] == col : this.props.axes.length > 0 ? this.props.axes[0] == col : false; -- cgit v1.2.3-70-g09d2 From a8cde5790f1f388dfed4dcf863d5284942773c94 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 25 Aug 2023 12:31:37 -0400 Subject: from last --- src/client/views/nodes/DataVizBox/components/TableBox.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 9e2ce1c80..01f33dd80 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -153,15 +153,18 @@ export class TableBox extends React.Component { key={rowId} className="table-row" onClick={action(e => { - // selecting a row + const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null); if (e.metaKey) { - const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null); + // highlighting a row if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1); else highlited?.push(rowId); } else { + // selecting a row const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null); - if (selected?.includes(rowId)) selected.splice(selected.indexOf(rowId), 1); - else selected?.push(rowId); + if (selected?.includes(rowId)) { + if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1); + selected.splice(selected.indexOf(rowId), 1); + } else selected?.push(rowId); } e.stopPropagation(); })} -- cgit v1.2.3-70-g09d2 From dca2fef8f8d4e068d9a5ab9ef6cd5636190d2ceb Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 25 Aug 2023 12:35:46 -0400 Subject: from last --- src/client/views/nodes/DataVizBox/components/TableBox.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 01f33dd80..067dff07a 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -154,13 +154,14 @@ export class TableBox extends React.Component { className="table-row" onClick={action(e => { const highlited = Cast(this.props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null); + const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null); if (e.metaKey) { // highlighting a row if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1); else highlited?.push(rowId); + if (!selected?.includes(rowId)) selected?.push(rowId); } else { // selecting a row - const selected = Cast(this.props.layoutDoc.dataViz_selectedRows, listSpec('number'), null); if (selected?.includes(rowId)) { if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1); selected.splice(selected.indexOf(rowId), 1); -- cgit v1.2.3-70-g09d2 From d9f396e297bfb3d41e1fe15f4b143d9916001d94 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Fri, 25 Aug 2023 12:51:16 -0400 Subject: convert url to base64, ensure img is loaded --- package-lock.json | 497 ++++++++++----------- src/client/views/nodes/ImageBox.tsx | 10 +- .../views/nodes/generativeFill/GenerativeFill.tsx | 31 +- .../generativeFillUtils/ImageHandler.ts | 28 ++ 4 files changed, 287 insertions(+), 279 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index 31a29409b..af5bddbf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142,24 +142,24 @@ "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==" }, "@babel/core": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz", - "integrity": "sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz", + "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.10", "@babel/generator": "^7.22.10", "@babel/helper-compilation-targets": "^7.22.10", "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.10", - "@babel/parser": "^7.22.10", + "@babel/helpers": "^7.22.11", + "@babel/parser": "^7.22.11", "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.10", - "@babel/types": "^7.22.10", + "@babel/traverse": "^7.22.11", + "@babel/types": "^7.22.11", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", + "json5": "^2.2.3", "semver": "^6.3.1" }, "dependencies": { @@ -194,14 +194,14 @@ } }, "@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==" + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz", + "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g==" }, "@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", + "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", "requires": { "@babel/code-frame": "^7.22.10", "@babel/generator": "^7.22.10", @@ -209,16 +209,16 @@ "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", + "@babel/parser": "^7.22.11", + "@babel/types": "^7.22.11", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", + "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", "requires": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5", @@ -278,9 +278,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", + "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", "requires": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5", @@ -322,9 +322,9 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz", - "integrity": "sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.11.tgz", + "integrity": "sha512-y1grdYL4WzmUDBRGK0pDbIoFd7UZKoDurDzWEoNMYoj1EL+foGRQNyPWDcC+YyegN5y1DUsFFmzjGijB3nSVAQ==", "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5", @@ -557,9 +557,9 @@ }, "dependencies": { "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", + "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", "requires": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5", @@ -569,13 +569,13 @@ } }, "@babel/helpers": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.10.tgz", - "integrity": "sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz", + "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==", "requires": { "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.10", - "@babel/types": "^7.22.10" + "@babel/traverse": "^7.22.11", + "@babel/types": "^7.22.11" }, "dependencies": { "@babel/code-frame": { @@ -609,14 +609,14 @@ } }, "@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==" + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz", + "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g==" }, "@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", + "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", "requires": { "@babel/code-frame": "^7.22.10", "@babel/generator": "^7.22.10", @@ -624,16 +624,16 @@ "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", + "@babel/parser": "^7.22.11", + "@babel/types": "^7.22.11", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", + "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", "requires": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5", @@ -941,9 +941,9 @@ } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz", - "integrity": "sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.11.tgz", + "integrity": "sha512-0pAlmeRJn6wU84zzZsEOx1JV1Jf8fqO9ok7wofIJwUnplYo247dcd24P+cMJht7ts9xkzdtB0EPHmOb7F+KzXw==", "requires": { "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", @@ -1022,11 +1022,11 @@ } }, "@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", + "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, @@ -1132,9 +1132,9 @@ } }, "@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", + "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3" @@ -1164,9 +1164,9 @@ } }, "@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", + "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -1212,9 +1212,9 @@ } }, "@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", + "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -1243,9 +1243,9 @@ } }, "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", + "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -1290,11 +1290,11 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.11.tgz", + "integrity": "sha512-o2+bg7GDS60cJMgz9jWqRUsWkMzLCxp+jFDeDUT5sjRlAxcJWZ2ylNdI7QQ2+CH5hWu7OnN+Cv3htt7AkSf96g==", "requires": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.9", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1307,12 +1307,12 @@ } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz", + "integrity": "sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==", "requires": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.9", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5" }, @@ -1372,9 +1372,9 @@ } }, "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", + "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -1388,9 +1388,9 @@ } }, "@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", + "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -1404,12 +1404,12 @@ } }, "@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.11.tgz", + "integrity": "sha512-nX8cPFa6+UmbepISvlf5jhQyaC7ASs/7UxHmMkuJ/k5xSHvDPPaibMo+v3TXwU/Pjqhep/nFNpd3zn4YR59pnw==", "requires": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.10", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-transform-parameters": "^7.22.5" @@ -1439,9 +1439,9 @@ } }, "@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", + "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -1455,9 +1455,9 @@ } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz", - "integrity": "sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.11.tgz", + "integrity": "sha512-7X2vGqH2ZKu7Imx0C+o5OysRwtF/wzdCAqmcD1N1v2Ww8CtOSC+p+VoV76skm47DLvBZ8kBFic+egqxM9S/p4g==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", @@ -1503,12 +1503,12 @@ } }, "@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", + "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, @@ -1733,12 +1733,12 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.10.tgz", - "integrity": "sha512-7++c8I/ymsDo4QQBAgbraXLzIM6jmfao11KgIBEYZRReWzNWH9NtNgJcyrZiXsOPh523FQm6LfpLyy/U5fn46A==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.11.tgz", + "integrity": "sha512-0E4/L+7gfvHub7wsbTv03oRtD69X31LByy44fGmFzbZScpupFByMcgCJ0VbBTkzyjSJKuRoGN8tcijOWKTmqOA==", "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.10", + "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-typescript": "^7.22.5" }, @@ -1914,9 +1914,9 @@ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" }, "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", + "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", "requires": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5", @@ -1961,15 +1961,15 @@ } }, "@babel/preset-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.5.tgz", - "integrity": "sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.11.tgz", + "integrity": "sha512-tWY5wyCZYBGY7IlalfKI1rLiGlIfnwsRHZqlky0HVv8qviwQ1Uo/05M6+s+TcTCVa6Bmoo2uJW5TMFX6Wa4qVg==", "requires": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.5", "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-typescript": "^7.22.5" + "@babel/plugin-transform-modules-commonjs": "^7.22.11", + "@babel/plugin-transform-typescript": "^7.22.11" }, "dependencies": { "@babel/helper-plugin-utils": { @@ -2693,6 +2693,36 @@ "resolve-url": "^0.2.1" } }, + "@floating-ui/core": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.4.1.tgz", + "integrity": "sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==", + "requires": { + "@floating-ui/utils": "^0.1.1" + } + }, + "@floating-ui/dom": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz", + "integrity": "sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==", + "requires": { + "@floating-ui/core": "^1.4.1", + "@floating-ui/utils": "^0.1.1" + } + }, + "@floating-ui/react-dom": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", + "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==", + "requires": { + "@floating-ui/dom": "^1.3.0" + } + }, + "@floating-ui/utils": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz", + "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==" + }, "@fortawesome/fontawesome-common-types": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", @@ -3202,18 +3232,18 @@ } }, "@mui/styled-engine-sc": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.12.0.tgz", - "integrity": "sha512-3MgYoY2YG5tx0E5oKqvCv94oL0ABVBr+qpcyvciXW/v0wzPG6bXvuZV80GHYlJfasgnnRa1AbRWf5a9FcX8v6g==", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.14.6.tgz", + "integrity": "sha512-7/KXXdDLjpQAmbmIhUs1x7nzqooEiHkidQOXCIH04NiVa4KRxP4v/bOWV/5GpgZi1Aky5ruf9IVyH3jxYIW3JA==", "requires": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.22.10", "prop-types": "^15.8.1" }, "dependencies": { "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -6507,9 +6537,9 @@ } }, "browndash-components": { - "version": "0.0.92", - "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.92.tgz", - "integrity": "sha512-eE/6WQNZiLnaXUKyoaMm0PDYjExUsFJ9VTAIIxROpYPosIBKWNZ743xaOfmehib5us9hEXJb0CvUFJQb8rzDVw==", + "version": "0.0.97", + "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.97.tgz", + "integrity": "sha512-UO8NQrOJoAq+UQGR8TXCRzr6jX9qSnEot9FHP7zdwlKH1sGntbQpyM5BXdwfkyXo+oh1qstTGkyR4s20CY7Yrw==", "requires": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -6627,14 +6657,15 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "@mui/base": { - "version": "5.0.0-beta.11", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.11.tgz", - "integrity": "sha512-FdKZGPd8qmC3ZNke7CNhzcEgToc02M6WYZc9hcBsNQ17bgAd3s9F//1bDDYgMVBYxDM71V0sv/hBHlOY4I1ZVA==", + "version": "5.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.12.tgz", + "integrity": "sha512-tZjjXNAyUpwSDT1uRliZMhRQkWYzELJ8Qi61EuOMRpi36HIwnK2T7Nr4RI423Sv8G2EEikDAZj7je33eNd73NQ==", "requires": { - "@babel/runtime": "^7.22.6", + "@babel/runtime": "^7.22.10", "@emotion/is-prop-valid": "^1.2.1", + "@floating-ui/react-dom": "^2.0.1", "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.5", + "@mui/utils": "^5.14.6", "@popperjs/core": "^2.11.8", "clsx": "^2.0.0", "prop-types": "^15.8.1", @@ -6642,9 +6673,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -6652,21 +6683,21 @@ } }, "@mui/core-downloads-tracker": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.5.tgz", - "integrity": "sha512-+wpGH1USwPcKMFPMvXqYPC6fEvhxM3FzxC8lyDiNK/imLyyJ6y2DPb1Oue7OGIKJWBmYBqrWWtfovrxd1aJHTA==" + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.6.tgz", + "integrity": "sha512-QZEU3pyGWLuaHbxvOlShol7U1FVgzWBR0OH9H8D7L8w4/vto5N5jJVvlqFQS3T0zbR6YGHxFaiL6Ky87jQg7aw==" }, "@mui/material": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.5.tgz", - "integrity": "sha512-4qa4GMfuZH0Ai3mttk5ccXP8a3sf7aPlAJwyMrUSz6h9hPri6BPou94zeu3rENhhmKLby9S/W1y+pmficy8JKA==", - "requires": { - "@babel/runtime": "^7.22.6", - "@mui/base": "5.0.0-beta.11", - "@mui/core-downloads-tracker": "^5.14.5", - "@mui/system": "^5.14.5", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.6.tgz", + "integrity": "sha512-C3UgGrmtvcGkQkm0ONBU7bTdapTjQc2Se3b2354xMmU7lgSgW7VM6EP9wIH5XqqoJ60m9l/s9kbTWX0Y+EaWvA==", + "requires": { + "@babel/runtime": "^7.22.10", + "@mui/base": "5.0.0-beta.12", + "@mui/core-downloads-tracker": "^5.14.6", + "@mui/system": "^5.14.6", "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.5", + "@mui/utils": "^5.14.6", "@types/react-transition-group": "^4.4.6", "clsx": "^2.0.0", "csstype": "^3.1.2", @@ -6676,9 +6707,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -6686,19 +6717,19 @@ } }, "@mui/private-theming": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.5.tgz", - "integrity": "sha512-cC4C5RrpXpDaaZyH9QwmPhRLgz+f2SYbOty3cPkk4qPSOSfif2ZEcDD9HTENKDDd9deB+xkPKzzZhi8cxIx8Ig==", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.6.tgz", + "integrity": "sha512-3VBLFGizBXfofyk33bwRg6t9L648aKnLmOKPfY1wFuiXq3AEYwobK65iDci/tHKxm/VKbZ6A7PFjLejvB3EvRQ==", "requires": { - "@babel/runtime": "^7.22.6", - "@mui/utils": "^5.14.5", + "@babel/runtime": "^7.22.10", + "@mui/utils": "^5.14.6", "prop-types": "^15.8.1" }, "dependencies": { "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -6706,20 +6737,20 @@ } }, "@mui/styled-engine": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", - "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.6.tgz", + "integrity": "sha512-I6zeu/OP1Hk4NsX1Oj85TiYl1dER0JMsLJVn76J1Ihl24A5EbiZQKJp3Mn+ufA79ypkdAvM9aQCAQyiVBFcUHg==", "requires": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.22.10", "@emotion/cache": "^11.11.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" }, "dependencies": { "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -6727,24 +6758,24 @@ } }, "@mui/system": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.5.tgz", - "integrity": "sha512-mextXZHDeGcR7E1kx43TRARrVXy+gI4wzpUgNv7MqZs1dvTVXQGVeAT6ydj9d6FUqHBPMNLGV/21vJOrpqsL+w==", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.6.tgz", + "integrity": "sha512-/n0ae1MegWjiV1BpRU8jgg4E0zBjeB2VYsT/68ag/xaDuq3/TaDKJeT9REIvyBvwlG3CI3S2O+tRELktxCD1kg==", "requires": { - "@babel/runtime": "^7.22.6", - "@mui/private-theming": "^5.14.5", - "@mui/styled-engine": "^5.13.2", + "@babel/runtime": "^7.22.10", + "@mui/private-theming": "^5.14.6", + "@mui/styled-engine": "^5.14.6", "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.5", + "@mui/utils": "^5.14.6", "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" }, "dependencies": { "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -6752,11 +6783,11 @@ } }, "@mui/utils": { - "version": "5.14.5", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.5.tgz", - "integrity": "sha512-6Hzw63VR9C5xYv+CbjndoRLU6Gntal8rJ5W+GUzkyHrGWIyYPWZPa6AevnyGioySNETATe1H9oXS8f/7qgIHJA==", + "version": "5.14.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.6.tgz", + "integrity": "sha512-AznpqLu6hrFnpHgcvsSSMCG+cDbkcCYfo+daUwBVReNYv4l+NQ8+wvBAF4aUMi155N7xWbbgh0cyKs6Wdsm3aA==", "requires": { - "@babel/runtime": "^7.22.6", + "@babel/runtime": "^7.22.10", "@types/prop-types": "^15.7.5", "@types/react-is": "^18.2.1", "prop-types": "^15.8.1", @@ -6764,9 +6795,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -6911,9 +6942,9 @@ } }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "optional": true }, "glob-parent": { @@ -7063,97 +7094,12 @@ "strip-ansi": "^7.0.1" } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "strip-ansi": { "version": "7.1.0", "bundled": true, "requires": { "ansi-regex": "^6.0.1" } - }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - } - } - }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - } - } } } }, @@ -8658,7 +8604,7 @@ } }, "string-width-cjs": { - "version": "npm:string-width-cjs@4.2.3", + "version": "npm:string-width@4.2.3", "bundled": true, "requires": { "emoji-regex": "^8.0.0", @@ -8681,7 +8627,7 @@ } }, "strip-ansi-cjs": { - "version": "npm:strip-ansi-cjs@6.0.1", + "version": "npm:strip-ansi@6.0.1", "bundled": true, "requires": { "ansi-regex": "^5.0.1" @@ -8840,7 +8786,7 @@ } }, "wrap-ansi-cjs": { - "version": "npm:wrap-ansi-cjs@7.0.0", + "version": "npm:wrap-ansi@7.0.0", "bundled": true, "requires": { "ansi-styles": "^4.0.0", @@ -10031,11 +9977,34 @@ "integrity": "sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==" }, "core-js-compat": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz", - "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==", + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.1.tgz", + "integrity": "sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA==", "requires": { - "browserslist": "^4.21.9" + "browserslist": "^4.21.10" + }, + "dependencies": { + "browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "requires": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + } + }, + "caniuse-lite": { + "version": "1.0.30001522", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz", + "integrity": "sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==" + }, + "electron-to-chromium": { + "version": "1.4.501", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.501.tgz", + "integrity": "sha512-NCF5hZUg73MEP0guvIM+BjPs9W07UeAuc5XCNqRZZTKJxLjE0ZS/Zo5UsV8bbs2y/jeKRPFPzdWdBfOGEZTXKg==" + } } }, "core-js-pure": { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 2c8e97512..f5c6a9273 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -59,8 +59,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent boolean) | undefined; - @action public static setImageEditorOpen(open: boolean) {ImageBox.imageEditorOpen = open;} - @action public static setImageEditorSource(source: string) {ImageBox.imageEditorSource = source;} + @action public static setImageEditorOpen(open: boolean) { + ImageBox.imageEditorOpen = open; + } + @action public static setImageEditorSource(source: string) { + ImageBox.imageEditorSource = source; + } private _ignoreScroll = false; private _forcedScroll = false; private _dropDisposer?: DragManager.DragDropDisposer; @@ -307,7 +311,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - if (!imageEditorSource || imageEditorSource === '') return; - const img = new Image(); - img.src = imageEditorSource; - currImg.current = img; - originalImg.current = img; - img.onload = () => { - const imgWidth = img.naturalWidth; - const imgHeight = img.naturalHeight; - const scale = Math.min(canvasSize / imgWidth, canvasSize / imgHeight); - const width = imgWidth * scale; - const height = imgHeight * scale; - setCanvasDims({ width, height }); + const loadInitial = async () => { + if (!imageEditorSource || imageEditorSource === '') return; + const img = new Image(); + const res = await ImageUtility.urlToBase64(imageEditorSource); + if (!res) return; + img.src = `data:image/png;base64,${res}`; + + img.onload = () => { + currImg.current = img; + originalImg.current = img; + const imgWidth = img.naturalWidth; + const imgHeight = img.naturalHeight; + const scale = Math.min(canvasSize / imgWidth, canvasSize / imgHeight); + const width = imgWidth * scale; + const height = imgHeight * scale; + setCanvasDims({ width, height }); + }; }; + loadInitial(); + // cleanup return () => { setInput(''); diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts index 2ede625f6..47a14135f 100644 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts @@ -283,4 +283,32 @@ export class ImageUtility { } return canvas; }; + + /** + * Converts a url to base64 (tainted canvas workaround) + */ + static urlToBase64 = async (imageUrl: string): Promise => { + try { + const res = await fetch(imageUrl); + const blob = await res.blob(); + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const base64Data = reader.result?.toString().split(',')[1]; + if (base64Data) { + resolve(base64Data); + } else { + reject(new Error('Failed to convert.')); + } + }; + reader.onerror = () => { + reject(new Error('Error reading image data')); + }; + reader.readAsDataURL(blob); + }); + } catch (err) { + console.error(err); + } + }; } -- cgit v1.2.3-70-g09d2 From cf5ffaf0dcf4c2b8ad9e408026b494106401db8e Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Fri, 25 Aug 2023 13:42:41 -0400 Subject: changed icon, added gen ai doc link --- src/client/util/RTFMarkup.tsx | 12 +++++++++- src/client/views/ContextMenuItem.tsx | 28 +++++++++++++++------- src/client/views/MainView.tsx | 3 ++- src/client/views/nodes/DocumentView.tsx | 4 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 ++-- .../nodes/generativeFill/GenerativeFillButtons.tsx | 4 +++- 6 files changed, 39 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index a0fc617ab..78069d323 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -3,6 +3,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { MainViewModal } from '../views/MainViewModal'; import { SettingsManager } from './SettingsManager'; +import { Doc } from '../../fields/Doc'; +import { StrCast } from '../../fields/Types'; @observer export class RTFMarkup extends React.Component<{}> { @@ -133,6 +135,14 @@ export class RTFMarkup extends React.Component<{}> { } render() { - return ; + return ( + + ); } } diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index daa2c152a..445406a89 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -11,7 +11,7 @@ export interface OriginalMenuProps { description: string; event: (stuff?: any) => void; undoable?: boolean; - icon: IconProp; //maybe should be optional (icon?) + icon: IconProp | JSX.Element; //maybe should be optional (icon?) closeMenu?: () => void; } @@ -82,19 +82,28 @@ export class ContextMenuItem extends React.Component {this.props.icon ? ( - - - + this.isJSXElement(this.props.icon) ? ( + {this.props.icon} + ) : ( + + + + ) ) : null}
{this.props.description.replace(':', '')}
-
@@ -110,7 +119,7 @@ export class ContextMenuItem extends React.Component 0 ? '90%' : '20%', marginTop, - background: StrCast(Doc.UserDoc().userBackgroundColor) + background: StrCast(Doc.UserDoc().userBackgroundColor), }}> {this._items.map(prop => ( @@ -141,9 +150,10 @@ export class ContextMenuItem extends React.Component
-
{submenu} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a785ffd42..8565941fd 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -447,6 +447,7 @@ export class MainView extends React.Component { fa.faSortUp, fa.faSortDown, fa.faTable, + fa.faTableColumns, fa.faTh, fa.faThList, fa.faProjectDiagram, @@ -1019,7 +1020,7 @@ export class MainView extends React.Component { - {/* */} + {/* */}
); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 533a047b1..b72864567 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -748,7 +748,7 @@ export class DocumentViewInternal extends DocComponent (this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined))), icon: 'hand-point-up', }); - !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'compass' }); + !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' }); } onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' }); @@ -806,7 +806,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' }); + constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'table-columns' }); cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' }); const help = cm.findByDescription('Help...'); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index da277826a..200d06a0b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -71,6 +71,7 @@ import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup'; +import { BsMarkdownFill } from 'react-icons/bs'; const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @@ -899,7 +900,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: this.Document._layout_autoHeight ? 'lock' : 'unlock', }); - optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: 'text' }); + optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; @@ -930,7 +931,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text); GPTPopup.Instance?.setTextAnchor(this.getAnchor(false)); GPTPopup.Instance?.setImgTargetDoc(this.rootDoc); GPTPopup.Instance.addToCollection = this.props.addDocument; diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx index 0dfcebea3..10eca358e 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx @@ -2,7 +2,8 @@ import './GenerativeFillButtons.scss'; import React = require('react'); import ReactLoading from 'react-loading'; import { activeColor } from './generativeFillUtils/generativeFillConstants'; -import { Button, Type } from 'browndash-components'; +import { Button, IconButton, Type } from 'browndash-components'; +import { AiOutlineInfo } from 'react-icons/ai'; interface ButtonContainerProps { getEdit: () => Promise; @@ -35,6 +36,7 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => { }} /> )} + } onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
); }; -- cgit v1.2.3-70-g09d2 From 54c4f12c825cdaf618b9428aaaed84b0de00e0d4 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Fri, 25 Aug 2023 13:46:12 -0400 Subject: condense --- src/client/views/ContextMenuItem.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 445406a89..a93dd6343 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -90,15 +90,7 @@ export class ContextMenuItem extends React.Component - {this.props.icon ? ( - this.isJSXElement(this.props.icon) ? ( - {this.props.icon} - ) : ( - - - - ) - ) : null} + {this.props.icon ? {this.isJSXElement(this.props.icon) ? this.props.icon : } : null}
{this.props.description.replace(':', '')}
Date: Fri, 25 Aug 2023 14:25:55 -0400 Subject: changed pres icon to svg --- deploy/assets/presTrailsSvg.svg | 58 +++++++++++++++ src/client/views/nodes/FontIconBox/FontIconBox.tsx | 4 +- src/client/views/nodes/FontIconBox/TrailsIcon.tsx | 83 ++++++++++++++++++++++ 3 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 deploy/assets/presTrailsSvg.svg create mode 100644 src/client/views/nodes/FontIconBox/TrailsIcon.tsx (limited to 'src') diff --git a/deploy/assets/presTrailsSvg.svg b/deploy/assets/presTrailsSvg.svg new file mode 100644 index 000000000..238e9979f --- /dev/null +++ b/deploy/assets/presTrailsSvg.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 47dac651d..225df555b 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -21,6 +21,7 @@ import { OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { RichTextMenu } from '../formattedText/RichTextMenu'; import './FontIconBox.scss'; +import TrailsIcon from './TrailsIcon'; export enum ButtonType { TextButton = 'textBtn', @@ -92,8 +93,7 @@ export class FontIconBox extends DocComponent() { else return null; } icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; - const trailsIcon = () => ; - return !icon ? null : icon === 'pres-trail' ? trailsIcon() : ; + return !icon ? null : icon === 'pres-trail' ? : ; }; @computed get dropdown() { return BoolCast(this.rootDoc.dropDownOpen); diff --git a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx new file mode 100644 index 000000000..99063b4a0 --- /dev/null +++ b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { StrCast } from '../../../../fields/Types'; +import { Doc } from '../../../../fields/Doc'; + +const TrailsIcon = () => ( + + + + + + + + + + + + + + + + +); + +export default TrailsIcon; -- cgit v1.2.3-70-g09d2 From 3960e2ed80317a14edaafeaeac07c9eeb405f513 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 26 Aug 2023 13:30:21 -0400 Subject: fixed color of trails icon --- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 4 ++-- src/client/views/nodes/FontIconBox/TrailsIcon.tsx | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 225df555b..d132707fa 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { colorMapping } from '../../../../server/DashSession/Session/utilities/session_config'; import { Utils } from '../../../../Utils'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -14,7 +15,6 @@ import { undoable, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { DocComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; -import { Colors } from '../../global/globalEnums'; import { SelectedDocView } from '../../selectedDoc'; import { StyleProp } from '../../StyleProvider'; import { OpenWhere } from '../DocumentView'; @@ -93,7 +93,7 @@ export class FontIconBox extends DocComponent() { else return null; } icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; - return !icon ? null : icon === 'pres-trail' ? : ; + return !icon ? null : icon === 'pres-trail' ? TrailsIcon(color) : ; }; @computed get dropdown() { return BoolCast(this.rootDoc.dropDownOpen); diff --git a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx index 99063b4a0..09fd6e3ae 100644 --- a/src/client/views/nodes/FontIconBox/TrailsIcon.tsx +++ b/src/client/views/nodes/FontIconBox/TrailsIcon.tsx @@ -1,10 +1,8 @@ import * as React from 'react'; -import { StrCast } from '../../../../fields/Types'; -import { Doc } from '../../../../fields/Doc'; -const TrailsIcon = () => ( +const TrailsIcon = (fill: string) => ( - +