From ef5948e183b15009615818e22e9ea9c748abce2c Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Thu, 10 Oct 2024 01:02:51 -0400
Subject: docCreatorMenu refactor work

---
 src/client/views/MainView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fc159d96b..6ee42d256 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -58,7 +58,7 @@ import { ImageLabelHandler } from './collections/collectionFreeForm/ImageLabelHa
 import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
 import { CollectionLinearView } from './collections/collectionLinear';
 import { LinkMenu } from './linking/LinkMenu';
-import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu';
+import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu';
 import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
 import { DocButtonState } from './nodes/DocumentLinksButton';
 import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
-- 
cgit v1.2.3-70-g09d2


From 3b17868560090756caf8b9b0f043ea163f2320e8 Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Sun, 20 Oct 2024 12:44:15 -0400
Subject: changes

---
 src/client/views/MainView.tsx                      |   5 +-
 src/client/views/StyleProviderQuiz.tsx             |   2 +-
 .../views/nodes/generativeFill/GenerativeFill.scss |  97 ---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 687 -------------------
 .../generativeFill/GenerativeFillButtons.scss      |   4 -
 .../nodes/generativeFill/GenerativeFillButtons.tsx |  72 --
 .../generativeFillUtils/BrushHandler.ts            |  35 -
 .../GenerativeFillMathHelpers.ts                   |   6 -
 .../generativeFillUtils/ImageHandler.ts            | 312 ---------
 .../generativeFillUtils/PointerHandler.ts          |  11 -
 .../generativeFillUtils/generativeFillConstants.ts |   9 -
 .../generativeFillInterfaces.ts                    |  20 -
 .../nodes/imageEditor/GenerativeFillButtons.scss   |   4 +
 .../nodes/imageEditor/GenerativeFillButtons.tsx    |  72 ++
 .../views/nodes/imageEditor/ImageEditor.scss       | 134 ++++
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 758 +++++++++++++++++++++
 .../views/nodes/imageEditor/ImageEditorButtons.tsx |  69 ++
 .../imageEditor/imageEditingUtils/ImageHandler.ts  | 312 +++++++++
 .../imageEditor/imageEditorUtils/BrushHandler.ts   |  35 +
 .../imageEditorUtils/GenerativeFillMathHelpers.ts  |   6 +
 .../imageEditor/imageEditorUtils/ImageHandler.ts   | 312 +++++++++
 .../imageEditor/imageEditorUtils/PointerHandler.ts |  11 +
 .../imageEditorUtils/imageEditorConstants.ts       |   9 +
 .../imageEditorUtils/imageEditorInterfaces.ts      |  38 ++
 .../imageEditor/imageToolUtils/BrushHandler.ts     |  35 +
 .../imageEditor/imageToolUtils/ImageHandler.ts     | 312 +++++++++
 src/client/views/smartdraw/AnnotationPalette.tsx   |   2 +-
 27 files changed, 2112 insertions(+), 1257 deletions(-)
 delete mode 100644 src/client/views/nodes/generativeFill/GenerativeFill.scss
 delete mode 100644 src/client/views/nodes/generativeFill/GenerativeFill.tsx
 delete mode 100644 src/client/views/nodes/generativeFill/GenerativeFillButtons.scss
 delete mode 100644 src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
 delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
 delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
 delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
 delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
 delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
 delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
 create mode 100644 src/client/views/nodes/imageEditor/GenerativeFillButtons.scss
 create mode 100644 src/client/views/nodes/imageEditor/GenerativeFillButtons.tsx
 create mode 100644 src/client/views/nodes/imageEditor/ImageEditor.scss
 create mode 100644 src/client/views/nodes/imageEditor/ImageEditor.tsx
 create mode 100644 src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
 create mode 100644 src/client/views/nodes/imageEditor/imageEditingUtils/ImageHandler.ts
 create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts
 create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/GenerativeFillMathHelpers.ts
 create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
 create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/PointerHandler.ts
 create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts
 create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
 create mode 100644 src/client/views/nodes/imageEditor/imageToolUtils/BrushHandler.ts
 create mode 100644 src/client/views/nodes/imageEditor/imageToolUtils/ImageHandler.ts

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7779d339f..7bc211036 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -73,7 +73,7 @@ import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
 import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
-import GenerativeFill from './nodes/generativeFill/GenerativeFill';
+import ImageEditorBox from './nodes/imageEditor/ImageEditor';
 import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
@@ -446,6 +446,7 @@ export class MainView extends ObservableReactComponent<object> {
                 fa.faAlignRight,
                 fa.faHeading,
                 fa.faRulerCombined,
+                fa.faFill,
                 fa.faFillDrip,
                 fa.faLink,
                 fa.faUnlink,
@@ -1146,7 +1147,7 @@ export class MainView extends ObservableReactComponent<object> {
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} addSplit={CollectionDockingView.AddSplit} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
                 <GPTPopup key="gptpopup" />
                 <SchemaCSVPopUp key="schemacsvpopup" />
-                <GenerativeFill imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
+                <ImageEditorBox imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
             </div>
         );
     }
diff --git a/src/client/views/StyleProviderQuiz.tsx b/src/client/views/StyleProviderQuiz.tsx
index 1f2ad1485..8a1515d21 100644
--- a/src/client/views/StyleProviderQuiz.tsx
+++ b/src/client/views/StyleProviderQuiz.tsx
@@ -17,7 +17,7 @@ import { AnchorMenu } from './pdf/AnchorMenu';
 import { DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
 import { ImageBox } from './nodes/ImageBox';
-import { ImageUtility } from './nodes/generativeFill/generativeFillUtils/ImageHandler';
+import { ImageUtility } from './nodes/imageEditor/imageEditorUtils/ImageHandler';
 import './StyleProviderQuiz.scss';
 
 export namespace styleProviderQuiz {
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss
deleted file mode 100644
index c2669a950..000000000
--- a/src/client/views/nodes/generativeFill/GenerativeFill.scss
+++ /dev/null
@@ -1,97 +0,0 @@
-$navHeight: 5rem;
-$canvasSize: 1024px;
-$scale: 0.5;
-
-.generativeFillContainer {
-    position: absolute;
-    top: 0;
-    left: 0;
-    z-index: 9999;
-    height: 100vh;
-    width: 100vw;
-    display: flex;
-    flex-direction: column;
-    overflow: hidden;
-
-    .generativeFillControls {
-        flex-shrink: 0;
-        height: $navHeight;
-        color: #000000;
-        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
deleted file mode 100644
index 261eb4bb4..000000000
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ /dev/null
@@ -1,687 +0,0 @@
-/* eslint-disable jsx-a11y/label-has-associated-control */
-/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
-/* eslint-disable jsx-a11y/img-redundant-alt */
-/* eslint-disable jsx-a11y/click-events-have-key-events */
-/* eslint-disable react/function-component-definition */
-import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material';
-import { IconButton } from 'browndash-components';
-import * as React from 'react';
-import { useEffect, useRef, useState } from 'react';
-import { CgClose } from 'react-icons/cg';
-import { IoMdRedo, IoMdUndo } from 'react-icons/io';
-import { ClientUtils } from '../../../../ClientUtils';
-import { Doc, DocListCast } from '../../../../fields/Doc';
-import { List } from '../../../../fields/List';
-import { NumCast } from '../../../../fields/Types';
-import { Networking } from '../../../Network';
-import { DocUtils } from '../../../documents/DocUtils';
-import { Docs } from '../../../documents/Documents';
-import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
-import { ImageEditorData } from '../ImageBox';
-import { OpenWhereMod } from '../OpenWhere';
-import './GenerativeFill.scss';
-import { EditButtons, CutButtons } from './GenerativeFillButtons';
-import { BrushHandler, BrushType } from './generativeFillUtils/BrushHandler';
-import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
-import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
-import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
-import { DocumentView } from '../DocumentView';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { ImageField } from '../../../../fields/URLField';
-import { resolve } from 'url';
-
-interface GenerativeFillProps {
-    imageEditorOpen: boolean;
-    imageEditorSource: string;
-    imageRootDoc: Doc | undefined;
-    addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
-}
-
-// Added field on image doc: gen_fill_children: List of children Docs
-
-const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => {
-    const canvasRef = useRef<HTMLCanvasElement>(null);
-    const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
-    const drawingAreaRef = useRef<HTMLDivElement>(null);
-    const [cursorData, setCursorData] = useState<CursorData>({
-        x: 0,
-        y: 0,
-        width: 150,
-    });
-    const [isBrushing, setIsBrushing] = useState(false);
-    const [canvasScale, setCanvasScale] = useState(0.5);
-    // format: array of [image source, corresponding image Doc]
-    const [edits, setEdits] = useState<{ url: string; saveRes: Doc | undefined }[]>([]);
-    const [edited, setEdited] = useState(false);
-    // const [brushStyle] = useState<BrushStyle>(BrushStyle.ADD);
-    const [input, setInput] = useState('');
-    const [loading, setLoading] = useState(false);
-    const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
-        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<HTMLImageElement | null>(null);
-    // the unedited version of each generation (parent)
-    const originalImg = useRef<HTMLImageElement | null>(null);
-    const originalDoc = useRef<Doc | null>(null);
-    // stores history of data urls
-    const undoStack = useRef<string[]>([]);
-    // stores redo stack
-    const redoStack = useRef<string[]>([]);
-
-    // references to keep track of tree structure
-    const newCollectionRef = useRef<Doc | null>(null);
-    const parentDoc = useRef<Doc | null>(null);
-    const childrenDocs = useRef<Doc[]>([]);
-
-    // constants for image cutting
-    const cutPts = useRef<Point[]>([]);
-
-    // 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, canvasDims.width, canvasDims.height);
-        } else {
-            redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()];
-            const img = new Image();
-            img.src = target;
-            ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
-            undoStack.current = undoStack.current.slice(0, -1);
-        }
-    };
-
-    const handleRedo = () => {
-        const ctx = ImageUtility.getCanvasContext(canvasRef);
-        if (!ctx || !currImg.current || !canvasRef.current) return;
-
-        const target = redoStack.current[redoStack.current.length - 1];
-        if (target) {
-            undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()];
-            const img = new Image();
-            img.src = target;
-            ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
-            redoStack.current = redoStack.current.slice(0, -1);
-        }
-    };
-
-    // resets any erase strokes
-    const handleReset = () => {
-        if (!canvasRef.current || !currImg.current) return;
-        const ctx = ImageUtility.getCanvasContext(canvasRef);
-        if (!ctx) return;
-        ctx.clearRect(0, 0, canvasSize, canvasSize);
-        undoStack.current = [];
-        redoStack.current = [];
-        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
-    };
-
-    // initiate brushing
-    const handlePointerDown = (e: React.PointerEvent) => {
-        const canvas = canvasRef.current;
-        if (!canvas) return;
-        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);
-        BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor /* , brushStyle === BrushStyle.SUBTRACT */);
-    };
-
-    // stop brushing, push to undo stack
-    const handlePointerUp = () => {
-        const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
-        if (!ctx) return;
-        if (!isBrushing) return;
-        setIsBrushing(false);
-    };
-
-    // handles brushing on pointer movement
-    useEffect(() => {
-        if (!isBrushing) return undefined;
-        const canvas = canvasRef.current;
-        if (!canvas) return undefined;
-        const ctx = ImageUtility.getCanvasContext(canvasRef);
-        if (!ctx) return undefined;
-
-        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,
-            };
-            const pts = BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, BrushType.CUT);
-            cutPts.current.push(...pts);
-        };
-
-        drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove);
-        return () => {
-            drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove);
-        };
-    }, [isBrushing]);
-
-    // first load
-    useEffect(() => {
-        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('');
-            setEdited(false);
-            newCollectionRef.current = null;
-            parentDoc.current = null;
-            childrenDocs.current = [];
-            currImg.current = null;
-            originalImg.current = null;
-            originalDoc.current = null;
-            undoStack.current = [];
-            redoStack.current = [];
-            ImageUtility.clearCanvas(canvasRef);
-        };
-    }, [canvasRef, imageEditorSource]);
-
-    // once the appropriate dimensions are set, draw the image to the canvas
-    useEffect(() => {
-        if (!currImg.current) return;
-        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
-    }, [canvasDims]);
-
-    // 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;
-            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,
-        }));
-    };
-
-    // 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);
-        setEdited(true);
-        try {
-            const canvasOriginalImg = ImageUtility.getCanvasImg(img);
-            if (!canvasOriginalImg) return;
-            const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg);
-            if (!canvasMask) return;
-            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);
-
-            // create first image
-            if (!newCollectionRef.current) {
-                if (!isNewCollection && 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
-                    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' });
-
-                    // opening new tab
-                    CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
-
-                    // add the doc to the main freeform
-                    // eslint-disable-next-line no-use-before-define
-                    await createNewImgDoc(originalImg.current, true);
-                }
-            } else {
-                childrenDocs.current = [];
-            }
-
-            originalImg.current = currImg.current;
-            originalDoc.current = parentDoc.current;
-            const { urls } = res as APISuccess;
-            if (res.status !== 'error') {
-                const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
-                const imgRes = await Promise.all(
-                    imgUrls.map(async url => {
-                        // eslint-disable-next-line no-use-before-define
-                        const saveRes = await onSave(url);
-                        return { url, saveRes };
-                    })
-                );
-                setEdits(imgRes);
-                const image = new Image();
-                // eslint-disable-next-line prefer-destructuring
-                image.src = imgUrls[0];
-                ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
-                currImg.current = image;
-                parentDoc.current = imgRes[0].saveRes ?? null;
-            }
-        } catch (err) {
-            console.log(err);
-        }
-        setLoading(false);
-    };
-
-    const cutImage = async () => {
-        const img = currImg.current;
-        const canvas = canvasRef.current;
-        if (!canvas || !img) return;
-        canvas.width = img.naturalWidth;
-        canvas.height = img.naturalHeight;
-        const ctx = ImageUtility.getCanvasContext(canvasRef);
-        if (!ctx) return;
-        ctx.globalCompositeOperation = 'source-over';
-        setLoading(true);
-        setEdited(true);
-        // get the original image
-        const canvasOriginalImg = ImageUtility.getCanvasImg(img);
-        if (!canvasOriginalImg) return;
-        // draw the image onto the canvas
-        ctx.drawImage(img, 0, 0);
-        // get the mask which i assume is the thing the user draws on
-        // const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg);
-        // if (!canvasMask) return;
-        // canvasMask.width = canvas.width;
-        // canvasMask.height = canvas.height;
-        // now put the user's path around the mask
-        if (cutPts.current.length) {
-            ctx.beginPath();
-            ctx.moveTo(cutPts.current[0].x, cutPts.current[0].y); // later check edge case where cutPts is empty
-            for (let i = 0; i < cutPts.current.length; i++) {
-                ctx.lineTo(cutPts.current[i].x, cutPts.current[i].y);
-            }
-            ctx.closePath();
-            ctx.stroke();
-            ctx.fill();
-            // ctx.clip();
-        }
-        const url = canvas.toDataURL(); // this does the same thing as convert img to canvasurl
-        if (!newCollectionRef.current) {
-            if (!isNewCollection && 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
-                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' });
-                // opening new tab
-                CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
-            }
-        }
-        const image = new Image();
-        image.src = url;
-        await createNewImgDoc(image, true);
-        // add the doc to the main freeform
-        // eslint-disable-next-line no-use-before-define
-        setLoading(false);
-        cutPts.current.length = 0;
-    };
-
-    // adjusts all the img positions to be aligned
-    const adjustImgPositions = () => {
-        if (!parentDoc.current) return;
-        const startY = NumCast(parentDoc.current.y);
-        const children = DocListCast(parentDoc.current.gen_fill_children);
-        const len = children.length;
-        const initialYPositions: number[] = [];
-        for (let i = 0; i < len; i++) {
-            initialYPositions.push(startY + i * offsetDistanceY);
-        }
-        children.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<Doc | undefined> => {
-        if (!imageRootDoc) return undefined;
-        const { src } = img;
-        const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
-        const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
-
-        if (firstDoc) {
-            const x = 0;
-            const initialY = 0;
-            const newImg = Docs.Create.ImageDocument(source, {
-                x: x,
-                y: initialY,
-                _height: freeformRenderSize,
-                _width: freeformRenderSize,
-                data_nativeWidth: result.nativeWidth,
-                data_nativeHeight: result.nativeHeight,
-            });
-            if (isNewCollection && newCollectionRef.current) {
-                Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
-            } else {
-                addDoc?.(newImg);
-            }
-            parentDoc.current = newImg;
-            return newImg;
-        }
-        if (!parentDoc.current) return undefined;
-        const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
-        const initialY = 0;
-
-        const newImg = Docs.Create.ImageDocument(source, {
-            x: x,
-            y: initialY,
-            _height: freeformRenderSize,
-            _width: freeformRenderSize,
-            data_nativeWidth: result.nativeWidth,
-            data_nativeHeight: result.nativeHeight,
-        });
-
-        const parentList = DocListCast(parentDoc.current.gen_fill_children);
-        if (parentList.length > 0) {
-            parentList.push(newImg);
-            parentDoc.current.gen_fill_children = new List<Doc>(parentList);
-        } else {
-            parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
-        }
-
-        DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}` });
-        adjustImgPositions();
-
-        if (isNewCollection && newCollectionRef.current) {
-            Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
-        } else {
-            addDoc?.(newImg);
-        }
-        return newImg;
-    };
-
-    // Saves an image to the collection
-    const onSave = async (src: string) => {
-        const img = new Image();
-        img.src = src;
-        if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined;
-        try {
-            const res = await createNewImgDoc(img, false);
-            return res;
-        } catch (err) {
-            console.log(err);
-        }
-        return undefined;
-    };
-
-    // Closes the editor view
-    const handleViewClose = () => {
-        ImageEditorData.Open = false;
-        ImageEditorData.Source = '';
-        if (newCollectionRef.current) {
-            DocumentView.addViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
-        }
-        setEdits([]);
-    };
-
-    return (
-        <div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
-            <div className="generativeFillControls">
-                <h1>Image Editor</h1>
-                {/* <IconButton text="Cut out" icon={<FontAwesomeIcon icon="scissors" />} /> */}
-                <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
-                    <FormControlLabel
-                        control={
-                            <Checkbox
-                                // disable once edited has been clicked (doesn't make sense to change after first edit)
-                                disabled={edited}
-                                checked={isNewCollection}
-                                onChange={() => {
-                                    setIsNewCollection(prev => !prev);
-                                }}
-                            />
-                        }
-                        label="Create New Collection"
-                        labelPlacement="end"
-                        sx={{ whiteSpace: 'nowrap' }}
-                    />
-                    <EditButtons onClick={getEdit} loading={loading} onReset={handleReset} />
-                    <CutButtons onClick={cutImage} loading={loading} onReset={handleReset} />
-                    <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
-                </div>
-            </div>
-            {/* Main canvas for editing */}
-            <div
-                className="drawingArea" // this only works if pointerevents: none is set on the custom pointer
-                ref={drawingAreaRef}
-                onPointerOver={updateCursorData}
-                onPointerMove={updateCursorData}
-                onPointerDown={handlePointerDown}
-                onPointerUp={handlePointerUp}>
-                <canvas ref={canvasRef} width={canvasDims.width} height={canvasDims.height} style={{ transform: `scale(${canvasScale})` }} />
-                <canvas ref={canvasBackgroundRef} width={canvasDims.width} height={canvasDims.height} style={{ transform: `scale(${canvasScale})` }} />
-                <div
-                    className="pointer"
-                    style={{
-                        left: cursorData.x,
-                        top: cursorData.y,
-                        width: cursorData.width,
-                        height: cursorData.width,
-                    }}>
-                    <div className="innerPointer" />
-                </div>
-                {/* Icons */}
-                <div className="iconContainer">
-                    {/* Undo and Redo */}
-                    <IconButton
-                        style={{ cursor: 'pointer' }}
-                        onPointerDown={e => {
-                            e.stopPropagation();
-                            handleUndo();
-                        }}
-                        onPointerUp={e => {
-                            e.stopPropagation();
-                        }}
-                        color={activeColor}
-                        tooltip="Undo"
-                        icon={<IoMdUndo />}
-                    />
-                    <IconButton
-                        style={{ cursor: 'pointer' }}
-                        onPointerDown={e => {
-                            e.stopPropagation();
-                            handleRedo();
-                        }}
-                        onPointerUp={e => {
-                            e.stopPropagation();
-                        }}
-                        color={activeColor}
-                        tooltip="Redo"
-                        icon={<IoMdRedo />}
-                    />
-                    <div onPointerDown={e => e.stopPropagation()} style={{ height: 225, width: '100%', display: 'flex', justifyContent: 'center', cursor: 'pointer' }}>
-                        <Slider
-                            sx={{
-                                '& input[type="range"]': {
-                                    WebkitAppearance: 'slider-vertical',
-                                },
-                            }}
-                            orientation="vertical"
-                            min={25}
-                            max={500}
-                            defaultValue={150}
-                            size="small"
-                            valueLabelDisplay="auto"
-                            onChange={(e: any, val: any) => {
-                                setCursorData(prev => ({ ...prev, width: val as number }));
-                            }}
-                        />
-                    </div>
-                    <div onPointerDown={e => e.stopPropagation()} style={{ height: 225, width: '100%', display: 'flex', justifyContent: 'center', cursor: 'pointer' }}>
-                        <Slider
-                            sx={{
-                                '& input[type="range"]': {
-                                    WebkitAppearance: 'slider-vertical',
-                                },
-                            }}
-                            orientation="vertical"
-                            min={1}
-                            max={500}
-                            defaultValue={150}
-                            size="small"
-                            valueLabelDisplay="auto"
-                            onChange={(e: any, val: any) => {
-                                setCursorData(prev => ({ ...prev, width: val as number }));
-                            }}
-                        />
-                    </div>
-                </div>
-                {/* Edits thumbnails */}
-                <div className="editsBox">
-                    {edits.map((edit, i) => (
-                        <img
-                            // eslint-disable-next-line react/no-array-index-key
-                            key={i}
-                            alt="image edits"
-                            width={75}
-                            src={edit.url}
-                            style={{ cursor: 'pointer' }}
-                            onClick={async () => {
-                                const img = new Image();
-                                img.src = edit.url;
-                                ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
-                                currImg.current = img;
-                                parentDoc.current = edit.saveRes ?? null;
-                            }}
-                        />
-                    ))}
-                    {/* Original img thumbnail */}
-                    {edits.length > 0 && (
-                        <div style={{ position: 'relative' }}>
-                            <label
-                                style={{
-                                    position: 'absolute',
-                                    bottom: 10,
-                                    left: 10,
-                                    color: '#ffffff',
-                                    fontSize: '0.8rem',
-                                    letterSpacing: '1px',
-                                    textTransform: 'uppercase',
-                                }}>
-                                Original
-                            </label>
-                            <img
-                                alt="image stuff"
-                                width={75}
-                                src={originalImg.current?.src}
-                                style={{ cursor: 'pointer' }}
-                                onClick={() => {
-                                    if (!originalImg.current) return;
-                                    const img = new Image();
-                                    img.src = originalImg.current.src;
-                                    ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
-                                    currImg.current = img;
-                                    parentDoc.current = originalDoc.current;
-                                }}
-                            />
-                        </div>
-                    )}
-                </div>
-            </div>
-            <div>
-                <TextField
-                    value={input}
-                    onChange={(e: any) => setInput(e.target.value)}
-                    disabled={isBrushing}
-                    type="text"
-                    label="Prompt"
-                    placeholder="Prompt..."
-                    InputLabelProps={{ style: { fontSize: '16px' } }}
-                    inputProps={{ style: { fontSize: '16px' } }}
-                    sx={{
-                        backgroundColor: '#ffffff',
-                        position: 'absolute',
-                        bottom: '16px',
-                        transform: 'translateX(calc(50vw - 50%))',
-                        width: 'calc(100vw - 64px)',
-                    }}
-                />
-            </div>
-        </div>
-    );
-};
-
-export default GenerativeFill;
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss b/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss
deleted file mode 100644
index 0180ef904..000000000
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-.generativeFillBtnContainer {
-    display: flex;
-    gap: 1rem;
-}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
deleted file mode 100644
index fe22b273d..000000000
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import './GenerativeFillButtons.scss';
-import * as React from 'react';
-import ReactLoading from 'react-loading';
-import { Button, IconButton, Type } from 'browndash-components';
-import { AiOutlineInfo } from 'react-icons/ai';
-import { activeColor } from './generativeFillUtils/generativeFillConstants';
-
-interface ButtonContainerProps {
-    onClick: () => Promise<void>;
-    loading: boolean;
-    onReset: () => void;
-}
-
-export function EditButtons({ loading, onClick: getEdit, onReset }: ButtonContainerProps) {
-    return (
-        <div className="generativeFillBtnContainer">
-            <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
-            {loading ? (
-                <Button
-                    text="GET EDITS"
-                    type={Type.TERT}
-                    color={activeColor}
-                    icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
-                    iconPlacement="right"
-                    onClick={() => {
-                        if (!loading) getEdit();
-                    }}
-                />
-            ) : (
-                <Button
-                    text="GET EDITS"
-                    type={Type.TERT}
-                    color={activeColor}
-                    onClick={() => {
-                        if (!loading) getEdit();
-                    }}
-                />
-            )}
-            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
-        </div>
-    );
-}
-
-export function CutButtons({ loading, onClick: cutImage, onReset }: ButtonContainerProps) {
-    return (
-        <div className="generativeFillBtnContainer">
-            <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
-            {loading ? (
-                <Button
-                    text="CUT IMAGE"
-                    type={Type.TERT}
-                    color={activeColor}
-                    icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
-                    iconPlacement="right"
-                    onClick={() => {
-                        if (!loading) cutImage();
-                    }}
-                />
-            ) : (
-                <Button
-                    text="CUT IMAGE"
-                    type={Type.TERT}
-                    color={activeColor}
-                    onClick={() => {
-                        if (!loading) cutImage();
-                    }}
-                />
-            )}
-            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
-        </div>
-    );
-}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
deleted file mode 100644
index 8a66d7347..000000000
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { GenerativeFillMathHelpers } from './GenerativeFillMathHelpers';
-import { eraserColor } from './generativeFillConstants';
-import { Point } from './generativeFillInterfaces';
-import { points } from '@turf/turf';
-
-export enum BrushType {
-    GEN_FILL,
-    CUT,
-}
-
-export class BrushHandler {
-    static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => {
-        ctx.globalCompositeOperation = 'destination-out';
-        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 createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, brushType: BrushType) => {
-        const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
-        const pts: Point[] = [];
-        for (let i = 0; i < dist; i += 5) {
-            const s = i / dist;
-            const x = startPoint.x * (1 - s) + endPoint.x * s;
-            const y = startPoint.y * (1 - s) + endPoint.y * s;
-            pts.push({ x: startPoint.x, y: startPoint.y });
-            BrushHandler.brushCircleOverlay(x, y, brushRadius, ctx, fillColor);
-        }
-        return pts;
-    };
-}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
deleted file mode 100644
index 6da8c3da0..000000000
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { Point } from './generativeFillInterfaces';
-
-export class GenerativeFillMathHelpers {
-    static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
-    static angleBetween = (p1: Point, p2: Point) => 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
deleted file mode 100644
index 24dba1778..000000000
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ /dev/null
@@ -1,312 +0,0 @@
-import { RefObject } from 'react';
-import { bgColor, canvasSize } from './generativeFillConstants';
-
-export interface APISuccess {
-    status: 'success';
-    urls: string[];
-}
-
-export interface APIError {
-    status: 'error';
-    message: string;
-}
-
-export class ImageUtility {
-    /**
-     *
-     * @param canvas Canvas to convert
-     * @returns Blob of canvas
-     */
-    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> =>
-        new Promise(resolve => {
-            canvas.toBlob(blob => {
-                if (blob) {
-                    resolve(blob);
-                }
-            }, 'image/png');
-        });
-
-    // 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;
-                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;
-        }
-        return undefined;
-    };
-
-    // converts an image to a canvas data url
-    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> =>
-        new Promise<string>((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;
-        });
-
-    // calls the openai api to get image edits
-    static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
-        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 ${process.env.OPENAI_KEY}`,
-                },
-                body: fd,
-            });
-            const data = await res.json();
-            console.log(data.data);
-            return {
-                status: 'success',
-                urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`),
-            };
-        } catch (err) {
-            console.log(err);
-            return { status: 'error', message: 'API error.' };
-        }
-    };
-
-    // mock api call
-    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({
-        status: 'success',
-        urls: [mockSrc, mockSrc, mockSrc],
-    });
-
-    // Gets the canvas rendering context of a canvas
-    static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
-        if (!canvasRef.current) return null;
-        const ctx = canvasRef.current.getContext('2d');
-        if (!ctx) return null;
-        return ctx;
-    };
-
-    // Helper for downloading the canvas (for debugging)
-    static downloadCanvas = (canvas: HTMLCanvasElement) => {
-        const url = canvas.toDataURL();
-        const downloadLink = document.createElement('a');
-        downloadLink.href = url;
-        downloadLink.download = 'canvas';
-
-        downloadLink.click();
-        downloadLink.remove();
-    };
-
-    // Download the canvas (for debugging)
-    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);
-        };
-    };
-
-    // Clears the canvas
-    static clearCanvas = (canvasRef: React.RefObject<HTMLCanvasElement>) => {
-        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<HTMLCanvasElement>, width: number, height: number) => {
-        const drawImg = (htmlImg: HTMLImageElement) => {
-            const ctx = this.getCanvasContext(canvasRef);
-            if (!ctx) return;
-            ctx.globalCompositeOperation = 'source-over';
-            ctx.clearRect(0, 0, width, height);
-            ctx.drawImage(htmlImg, 0, 0, width, height);
-        };
-
-        if (img.complete) {
-            drawImg(img);
-        } else {
-            img.onload = () => {
-                drawImg(img);
-            };
-        }
-    };
-
-    // Gets the image mask for the openai endpoint
-    static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
-        const canvas = document.createElement('canvas');
-        canvas.width = canvasSize;
-        canvas.height = canvasSize;
-        const ctx = canvas.getContext('2d');
-        if (!ctx) return undefined;
-        ctx?.clearRect(0, 0, canvasSize, canvasSize);
-        ctx.drawImage(paddedCanvas, 0, 0);
-
-        // 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;
-    };
-
-    // 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;
-        for (let i = 0; i < canvas.height; i++) {
-            for (let j = 0; j < xOffset; j++) {
-                const targetIdx = 4 * (i * canvas.width + j);
-                const sourceI = i;
-                const sourceJ = xOffset + (xOffset - j);
-                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
-                data[targetIdx] = data[sourceIdx];
-                data[targetIdx + 1] = data[sourceIdx + 1];
-                data[targetIdx + 2] = data[sourceIdx + 2];
-            }
-        }
-        for (let i = 0; i < canvas.height; i++) {
-            for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) {
-                const targetIdx = 4 * (i * canvas.width + j);
-                const sourceI = i;
-                const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j));
-                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
-                data[targetIdx] = data[sourceIdx];
-                data[targetIdx + 1] = data[sourceIdx + 1];
-                data[targetIdx + 2] = data[sourceIdx + 2];
-            }
-        }
-        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;
-        for (let j = 0; j < canvas.width; j++) {
-            for (let i = 0; i < yOffset; i++) {
-                const targetIdx = 4 * (i * canvas.width + j);
-                const sourceJ = j;
-                const sourceI = yOffset + (yOffset - i);
-                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
-                data[targetIdx] = data[sourceIdx];
-                data[targetIdx + 1] = data[sourceIdx + 1];
-                data[targetIdx + 2] = data[sourceIdx + 2];
-            }
-        }
-        for (let j = 0; j < canvas.width; j++) {
-            for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) {
-                const targetIdx = 4 * (i * canvas.width + j);
-                const sourceJ = j;
-                const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i));
-                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
-                data[targetIdx] = data[sourceIdx];
-                data[targetIdx + 1] = data[sourceIdx + 1];
-                data[targetIdx + 2] = data[sourceIdx + 2];
-            }
-        }
-        ctx.putImageData(imageData, 0, 0);
-    };
-
-    // 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;
-        canvas.height = canvasSize;
-        const ctx = canvas.getContext('2d');
-        if (!ctx) return undefined;
-        // fix scaling
-        const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
-        const width = Math.floor(img.width * scale);
-        const height = Math.floor(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 = Math.floor((canvasSize - width) / 2);
-            ctx.drawImage(img, xOffset, 0, width, height);
-
-            // draw reflected image padding
-            this.drawHorizontalReflection(ctx, canvas, xOffset);
-        } else {
-            // vertical padding, y offset
-            const yOffset = Math.floor((canvasSize - height) / 2);
-            ctx.drawImage(img, 0, yOffset, width, height);
-
-            // draw reflected image padding
-            this.drawVerticalReflection(ctx, canvas, yOffset);
-        }
-        return canvas;
-    };
-
-    /**
-     * Converts a url to base64 (tainted canvas workaround)
-     */
-    static urlToBase64 = async (imageUrl: string): Promise<string | undefined> => {
-        try {
-            const res = await fetch(imageUrl);
-            const blob = await res.blob();
-
-            return new Promise<string>((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);
-        }
-        return undefined;
-    };
-}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
deleted file mode 100644
index 260923a64..000000000
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-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
deleted file mode 100644
index 4772304bc..000000000
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export const canvasSize = 1024;
-export const freeformRenderSize = 300;
-export const offsetDistanceY = freeformRenderSize + 400;
-export const offsetX = 200;
-export const newCollectionSize = 500;
-
-export const activeColor = '#1976d2';
-export const eraserColor = '#e1e9ec';
-export const bgColor = '#f0f4f6';
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
deleted file mode 100644
index 1e7801056..000000000
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export interface CursorData {
-    x: number;
-    y: number;
-    width: number;
-}
-
-export interface Point {
-    x: number;
-    y: number;
-}
-
-export enum BrushMode {
-    ADD,
-    SUBTRACT,
-}
-
-export interface ImageDimensions {
-    width: number;
-    height: number;
-}
diff --git a/src/client/views/nodes/imageEditor/GenerativeFillButtons.scss b/src/client/views/nodes/imageEditor/GenerativeFillButtons.scss
new file mode 100644
index 000000000..0180ef904
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/GenerativeFillButtons.scss
@@ -0,0 +1,4 @@
+.generativeFillBtnContainer {
+    display: flex;
+    gap: 1rem;
+}
diff --git a/src/client/views/nodes/imageEditor/GenerativeFillButtons.tsx b/src/client/views/nodes/imageEditor/GenerativeFillButtons.tsx
new file mode 100644
index 000000000..32ed6b307
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/GenerativeFillButtons.tsx
@@ -0,0 +1,72 @@
+import './GenerativeFillButtons.scss';
+import * as React from 'react';
+import ReactLoading from 'react-loading';
+import { Button, IconButton, Type } from 'browndash-components';
+import { AiOutlineInfo } from 'react-icons/ai';
+import { activeColor } from './imageEditorUtils/imageEditorConstants';
+
+interface ButtonContainerProps {
+    onClick: () => Promise<void>;
+    loading: boolean;
+    onReset: () => void;
+}
+
+export function EditButtons({ loading, onClick: getEdit, onReset }: ButtonContainerProps) {
+    return (
+        <div className="generativeFillBtnContainer">
+            <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
+            {loading ? (
+                <Button
+                    text="GET EDITS"
+                    type={Type.TERT}
+                    color={activeColor}
+                    icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
+                    iconPlacement="right"
+                    onClick={() => {
+                        if (!loading) getEdit();
+                    }}
+                />
+            ) : (
+                <Button
+                    text="GET EDITS"
+                    type={Type.TERT}
+                    color={activeColor}
+                    onClick={() => {
+                        if (!loading) getEdit();
+                    }}
+                />
+            )}
+            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
+        </div>
+    );
+}
+
+export function CutButtons({ loading, onClick: cutImage, onReset }: ButtonContainerProps) {
+    return (
+        <div className="generativeFillBtnContainer">
+            <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
+            {loading ? (
+                <Button
+                    text="CUT IMAGE"
+                    type={Type.TERT}
+                    color={activeColor}
+                    icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
+                    iconPlacement="right"
+                    onClick={() => {
+                        if (!loading) cutImage();
+                    }}
+                />
+            ) : (
+                <Button
+                    text="CUT IMAGE"
+                    type={Type.TERT}
+                    color={activeColor}
+                    onClick={() => {
+                        if (!loading) cutImage();
+                    }}
+                />
+            )}
+            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
+        </div>
+    );
+}
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss
new file mode 100644
index 000000000..21c28f6da
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/ImageEditor.scss
@@ -0,0 +1,134 @@
+$navHeight: 5rem;
+$canvasSize: 1024px;
+$scale: 0.5;
+
+.imageEditorContainer {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 9999;
+    height: 100vh;
+    width: 100vw;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+
+    .imageEditorTopBar {
+        flex-shrink: 0;
+        height: $navHeight;
+        color: #000000;
+        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;
+
+        .imageEditorControls {
+            display: flex;
+            align-items: center;
+            gap: 1.5rem;
+        }
+
+        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: 3rem;
+            left: 2rem;
+            display: flex;
+            flex-direction: column;
+            gap: 4rem;
+
+            .imageToolsContainer {
+                display: flex;
+                flex-direction: column;
+                gap: 10px;
+            }
+
+            .undoRedoContainer {
+                justify-content: center;
+                display: flex;
+                flex-direction: row;
+            }
+
+            .sliderContainer {
+                height: 225px;
+                width: 100%;
+                display: flex;
+                justify-content: center;
+                cursor: pointer;
+            }
+        }
+
+        .editsBox {
+            position: absolute;
+            top: 2rem;
+            right: 2rem;
+            display: flex;
+            flex-direction: column;
+            gap: 1rem;
+
+            .originalImageLabel {
+                position: absolute;
+                bottom: 10;
+                left: 10;
+                color: #ffffff;
+                font-size: 0.8rem;
+                letter-spacing: 1px;
+                text-transform: uppercase;
+            }
+
+            img {
+                cursor: pointer;
+                transition: all 0.2s ease-in-out;
+                &:hover {
+                    opacity: 0.8;
+                }
+            }
+        }
+    }
+}
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
new file mode 100644
index 000000000..86f7d8d29
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -0,0 +1,758 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/img-redundant-alt */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable react/function-component-definition */
+import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material';
+import { IconButton } from 'browndash-components';
+import * as React from 'react';
+import { useEffect, useRef, useState } from 'react';
+import { CgClose } from 'react-icons/cg';
+import { IoMdRedo, IoMdUndo } from 'react-icons/io';
+import { ClientUtils } from '../../../../ClientUtils';
+import { Doc, DocListCast } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { NumCast } from '../../../../fields/Types';
+import { Networking } from '../../../Network';
+import { DocUtils } from '../../../documents/DocUtils';
+import { Docs } from '../../../documents/Documents';
+import { CollectionDockingView } from '../../collections/CollectionDockingView';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
+import { ImageEditorData } from '../ImageBox';
+import { OpenWhereMod } from '../OpenWhere';
+import './ImageEditor.scss';
+import { ApplyFuncButtons, ImageToolButton } from './ImageEditorButtons';
+import { BrushHandler, BrushType } from './imageEditorUtils/BrushHandler';
+import { APISuccess, ImageUtility } from './imageEditorUtils/ImageHandler';
+import { PointerHandler } from './imageEditorUtils/PointerHandler';
+import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants';
+import { CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces';
+import { DocumentView } from '../DocumentView';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { ImageField } from '../../../../fields/URLField';
+import { resolve } from 'url';
+import { DocData } from '../../../../fields/DocSymbols';
+
+interface GenerativeFillProps {
+    imageEditorOpen: boolean;
+    imageEditorSource: string;
+    imageRootDoc: Doc | undefined;
+    addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+}
+
+// Added field on image doc: gen_fill_children: List of children Docs
+
+const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => {
+    const canvasRef = useRef<HTMLCanvasElement>(null);
+    const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
+    const drawingAreaRef = useRef<HTMLDivElement>(null);
+    const [cursorData, setCursorData] = useState<CursorData>({
+        x: 0,
+        y: 0,
+        width: 150,
+    });
+    const [isBrushing, setIsBrushing] = useState(false);
+    const [canvasScale, setCanvasScale] = useState(0.5);
+    // format: array of [image source, corresponding image Doc]
+    const [edits, setEdits] = useState<{ url: string; saveRes: Doc | undefined }[]>([]);
+    const [edited, setEdited] = useState(false);
+    const isFirstDoc = useRef(true);
+    // const [brushStyle] = useState<BrushStyle>(BrushStyle.ADD);
+    const [input, setInput] = useState('');
+    const [loading, setLoading] = useState(false);
+    const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
+        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<HTMLImageElement | null>(null);
+    // the unedited version of each generation (parent)
+    const originalImg = useRef<HTMLImageElement | null>(null);
+    const originalDoc = useRef<Doc | null>(null);
+    // stores history of data urls
+    const undoStack = useRef<string[]>([]);
+    // stores redo stack
+    const redoStack = useRef<string[]>([]);
+
+    // references to keep track of tree structure
+    const newCollectionRef = useRef<Doc | null>(null);
+    const parentDoc = useRef<Doc | null>(null);
+    const childrenDocs = useRef<Doc[]>([]);
+
+    // constants for image cutting
+    const cutPts = useRef<Point[]>([]);
+
+    /**
+     *
+     * @param type The new tool type we are changing to
+     */
+    const changeTool = (type: ImageToolType) => {
+        switch (type) {
+            case ImageToolType.GenerativeFill:
+                setCurrTool(genFillTool);
+                setCursorData(prev => ({ ...prev, width: genFillTool.sliderDefault as number }));
+                break;
+            case ImageToolType.Cut:
+                setCurrTool(cutTool);
+                setCursorData(prev => ({ ...prev, width: cutTool.sliderDefault as number }));
+                break;
+            default:
+                break;
+        }
+    };
+    // 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, canvasDims.width, canvasDims.height);
+        } else {
+            redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()];
+            const img = new Image();
+            img.src = target;
+            ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
+            undoStack.current = undoStack.current.slice(0, -1);
+        }
+    };
+
+    const handleRedo = () => {
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx || !currImg.current || !canvasRef.current) return;
+
+        const target = redoStack.current[redoStack.current.length - 1];
+        if (target) {
+            undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()];
+            const img = new Image();
+            img.src = target;
+            ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
+            redoStack.current = redoStack.current.slice(0, -1);
+        }
+    };
+
+    // resets any erase strokes
+    const handleReset = () => {
+        if (!canvasRef.current || !currImg.current) return;
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx) return;
+        ctx.clearRect(0, 0, canvasSize, canvasSize);
+        undoStack.current = [];
+        redoStack.current = [];
+        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
+    };
+
+    // initiate brushing
+    const handlePointerDown = (e: React.PointerEvent) => {
+        const canvas = canvasRef.current;
+        if (!canvas) return;
+        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);
+        BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor /* , brushStyle === BrushStyle.SUBTRACT */);
+    };
+
+    // stop brushing, push to undo stack
+    const handlePointerUp = () => {
+        const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
+        if (!ctx) return;
+        if (!isBrushing) return;
+        setIsBrushing(false);
+    };
+
+    // handles brushing on pointer movement
+    useEffect(() => {
+        if (!isBrushing) return undefined;
+        const canvas = canvasRef.current;
+        if (!canvas) return undefined;
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx) return undefined;
+
+        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,
+            };
+            const pts = BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, BrushType.CUT);
+            cutPts.current.push(...pts);
+        };
+
+        drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove);
+        return () => {
+            drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove);
+        };
+    }, [isBrushing]);
+
+    // first load
+    useEffect(() => {
+        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('');
+            setEdited(false);
+            newCollectionRef.current = null;
+            parentDoc.current = null;
+            childrenDocs.current = [];
+            currImg.current = null;
+            originalImg.current = null;
+            originalDoc.current = null;
+            undoStack.current = [];
+            redoStack.current = [];
+            ImageUtility.clearCanvas(canvasRef);
+        };
+    }, [canvasRef, imageEditorSource]);
+
+    // once the appropriate dimensions are set, draw the image to the canvas
+    useEffect(() => {
+        if (!currImg.current) return;
+        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
+    }, [canvasDims]);
+
+    // 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;
+            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,
+        }));
+    };
+
+    // 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);
+        setEdited(true);
+        try {
+            const canvasOriginalImg = ImageUtility.getCanvasImg(img);
+            if (!canvasOriginalImg) return;
+            const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg);
+            if (!canvasMask) return;
+            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);
+
+            // create first image
+            if (!newCollectionRef.current) {
+                if (!isNewCollection && 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
+                    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' });
+
+                    // opening new tab
+                    CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+
+                    // add the doc to the main freeform
+                    // eslint-disable-next-line no-use-before-define
+                    await createNewImgDoc(originalImg.current, true);
+                }
+            } else {
+                childrenDocs.current = [];
+            }
+
+            originalImg.current = currImg.current;
+            originalDoc.current = parentDoc.current;
+            const { urls } = res as APISuccess;
+            if (res.status !== 'error') {
+                const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
+                const imgRes = await Promise.all(
+                    imgUrls.map(async url => {
+                        // eslint-disable-next-line no-use-before-define
+                        const saveRes = await onSave(url);
+                        return { url, saveRes };
+                    })
+                );
+                setEdits(imgRes);
+                const image = new Image();
+                // eslint-disable-next-line prefer-destructuring
+                image.src = imgUrls[0];
+                ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
+                currImg.current = image;
+                parentDoc.current = imgRes[0].saveRes ?? null;
+            }
+        } catch (err) {
+            console.log(err);
+        }
+        setLoading(false);
+    };
+
+    const cutImage = async () => {
+        const img = currImg.current;
+        const canvas = canvasRef.current;
+        if (!canvas || !img) return;
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx) return;
+        setLoading(true);
+        // get the original image
+        const canvasOriginalImg = ImageUtility.getCanvasImg(img);
+        if (!canvasOriginalImg) return;
+        // NOTE: cutting two diff shapes can be made possible by having the user press a button to set a new shape!
+        let minX = img.width;
+        let maxX = 0;
+        let minY = img.height;
+        let maxY = 0;
+        if (cutPts.current.length) {
+            ctx.beginPath();
+            ctx.moveTo(cutPts.current[0].x, cutPts.current[0].y); // later check edge case where cutPts is empty
+            for (let i = 0; i < cutPts.current.length; i++) {
+                ctx.lineTo(cutPts.current[i].x, cutPts.current[i].y);
+                minX = Math.min(cutPts.current[i].x, minX);
+                minY = Math.min(cutPts.current[i].y, minY);
+                maxX = Math.max(cutPts.current[i].x, maxX);
+                maxY = Math.max(cutPts.current[i].y, maxY);
+            }
+            ctx.closePath();
+            ctx.globalCompositeOperation = 'destination-in';
+            ctx.fill();
+        }
+
+        const url = canvas.toDataURL(); // this does the same thing as convert img to canvasurl
+        if (!newCollectionRef.current) {
+            if (!isNewCollection && 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
+                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' });
+                // opening new tab
+                CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+            }
+        }
+
+        const image = new Image();
+        image.src = url;
+        image.onload = async () => {
+            const croppedCanvas = document.createElement('canvas');
+            const croppedCtx = croppedCanvas.getContext('2d');
+            if (!croppedCtx) return;
+            croppedCanvas.width = maxX - minX;
+            croppedCanvas.height = maxY - minY;
+            croppedCtx.globalCompositeOperation = 'source-over';
+            croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
+            croppedCtx.drawImage(image, -minX, -minY);
+            const croppedURL = croppedCanvas.toDataURL();
+            const croppedImage = new Image();
+            croppedImage.src = croppedURL;
+
+            currImg.current = croppedImage;
+            const newImgDoc = await createNewImgDoc(croppedImage, isFirstDoc.current);
+            if (isFirstDoc.current) isFirstDoc.current = false;
+            if (newImgDoc) {
+                const docData = newImgDoc[DocData];
+                docData.backgroundColor = 'transparent';
+            }
+            setEdits(prevEdits => [...prevEdits, { url: croppedURL, saveRes: undefined }]);
+            setLoading(false);
+            cutPts.current.length = 0;
+        };
+    };
+
+    // adjusts all the img positions to be aligned
+    const adjustImgPositions = () => {
+        if (!parentDoc.current) return;
+        const startY = NumCast(parentDoc.current.y);
+        const children = DocListCast(parentDoc.current.gen_fill_children);
+        const len = children.length;
+        const initialYPositions: number[] = [];
+        for (let i = 0; i < len; i++) {
+            initialYPositions.push(startY + i * offsetDistanceY);
+        }
+        children.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, parent?: Doc): Promise<Doc | undefined> => {
+        if (!imageRootDoc) return undefined;
+        const { src } = img;
+        const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
+        const source = ClientUtils.prepend(result.accessPaths.agnostic.client);
+
+        if (firstDoc) {
+            const x = 0;
+            const initialY = 0;
+            const newImg = Docs.Create.ImageDocument(source, {
+                x: x,
+                y: initialY,
+                _height: freeformRenderSize,
+                _width: freeformRenderSize,
+                data_nativeWidth: result.nativeWidth,
+                data_nativeHeight: result.nativeHeight,
+            });
+            if (isNewCollection && newCollectionRef.current) {
+                Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            } else {
+                addDoc?.(newImg);
+            }
+            parentDoc.current = newImg;
+            return newImg;
+        }
+        if (!parentDoc.current) return undefined;
+        const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
+        const initialY = 0;
+
+        const newImg = Docs.Create.ImageDocument(source, {
+            x: x,
+            y: initialY,
+            _height: freeformRenderSize,
+            _width: freeformRenderSize,
+            data_nativeWidth: result.nativeWidth,
+            data_nativeHeight: result.nativeHeight,
+        });
+
+        const parentList = DocListCast(parentDoc.current.gen_fill_children);
+        if (parentList.length > 0) {
+            parentList.push(newImg);
+            parentDoc.current.gen_fill_children = new List<Doc>(parentList);
+        } else {
+            parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
+        }
+
+        DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}` });
+        adjustImgPositions();
+
+        if (isNewCollection && newCollectionRef.current) {
+            Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+        } else {
+            addDoc?.(newImg);
+        }
+        return newImg;
+    };
+
+    // Saves an image to the collection
+    const onSave = async (src: string) => {
+        const img = new Image();
+        img.src = src;
+        if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined;
+        try {
+            const res = await createNewImgDoc(img, false);
+            return res;
+        } catch (err) {
+            console.log(err);
+        }
+        return undefined;
+    };
+
+    // Closes the editor view
+    const handleViewClose = () => {
+        ImageEditorData.Open = false;
+        ImageEditorData.Source = '';
+        if (newCollectionRef.current) {
+            DocumentView.addViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
+        }
+        setEdits([]);
+    };
+
+    // defines the tools and sets current tool
+    const genFillTool: ImageEditTool = { type: ImageToolType.GenerativeFill, name: 'Generative Fill', btnText: 'GET EDITS', icon: 'fill', applyFunc: getEdit, sliderMin: 25, sliderMax: 500, sliderDefault: 150 };
+    const cutTool: ImageEditTool = { type: ImageToolType.Cut, name: 'Cut', btnText: 'CUT IMAGE', icon: 'scissors', applyFunc: cutImage, sliderMin: 1, sliderMax: 50, sliderDefault: 5 };
+    const imageEditTools: ImageEditTool[] = [genFillTool, cutTool];
+    const [currTool, setCurrTool] = useState<ImageEditTool>(genFillTool);
+
+    // the top controls for making a new collection, resetting, and applying edits,
+    function renderControls() {
+        return (
+            <div className="imageEditorTopBar">
+                <h1>Image Editor</h1>
+                {/* <IconButton text="Cut out" icon={<FontAwesomeIcon icon="scissors" />} /> */}
+                <div className="imageEditorControls">
+                    <FormControlLabel
+                        control={
+                            <Checkbox
+                                // disable once edited has been clicked (doesn't make sense to change after first edit)
+                                disabled={edited}
+                                checked={isNewCollection}
+                                onChange={() => {
+                                    setIsNewCollection(prev => !prev);
+                                }}
+                            />
+                        }
+                        label="Create New Collection"
+                        labelPlacement="end"
+                        sx={{ whiteSpace: 'nowrap' }}
+                    />
+                    <ApplyFuncButtons onClick={currTool.applyFunc} loading={loading} onReset={handleReset} btnText={currTool.btnText} />
+                    <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
+                </div>
+            </div>
+        );
+    }
+
+    // the side icons including tool type, the slider, and undo/redo
+    function renderSideIcons() {
+        return (
+            <div className="iconContainer">
+                <div className="imageToolsContainer">
+                    {imageEditTools.map(tool => {
+                        return ImageToolButton(tool, tool.type === currTool.type, changeTool);
+                    })}
+                </div>
+                <div className="sliderContainer" onPointerDown={e => e.stopPropagation()}>
+                    {currTool.type === ImageToolType.GenerativeFill && (
+                        <Slider
+                            sx={{
+                                '& input[type="range"]': {
+                                    WebkitAppearance: 'slider-vertical',
+                                },
+                            }}
+                            orientation="vertical"
+                            min={genFillTool.sliderMin}
+                            max={genFillTool.sliderMax}
+                            defaultValue={genFillTool.sliderDefault}
+                            size="small"
+                            valueLabelDisplay="auto"
+                            onChange={(e, val) => {
+                                setCursorData(prev => ({ ...prev, width: val as number }));
+                            }}
+                        />
+                    )}
+                    {currTool.type === ImageToolType.Cut && (
+                        <Slider
+                            sx={{
+                                '& input[type="range"]': {
+                                    WebkitAppearance: 'slider-vertical',
+                                },
+                            }}
+                            orientation="vertical"
+                            min={cutTool.sliderMin}
+                            max={cutTool.sliderMax}
+                            defaultValue={cutTool.sliderDefault}
+                            size="small"
+                            valueLabelDisplay="auto"
+                            onChange={(e, val) => {
+                                setCursorData(prev => ({ ...prev, width: val as number }));
+                            }}
+                        />
+                    )}
+                </div>
+                {/* Undo and Redo */}
+                <div className="undoRedoContainer">
+                    <IconButton
+                        style={{ cursor: 'pointer' }}
+                        onPointerDown={e => {
+                            e.stopPropagation();
+                            handleUndo();
+                        }}
+                        onPointerUp={e => {
+                            e.stopPropagation();
+                        }}
+                        color={activeColor}
+                        tooltip="Undo"
+                        icon={<IoMdUndo />}
+                    />
+                    <IconButton
+                        style={{ cursor: 'pointer' }}
+                        onPointerDown={e => {
+                            e.stopPropagation();
+                            handleRedo();
+                        }}
+                        onPointerUp={e => {
+                            e.stopPropagation();
+                        }}
+                        color={activeColor}
+                        tooltip="Redo"
+                        icon={<IoMdRedo />}
+                    />
+                </div>
+            </div>
+        );
+    }
+
+    // circular pointer for drawing/erasing
+    function renderPointer() {
+        return (
+            <div
+                className="pointer"
+                style={{
+                    left: cursorData.x,
+                    top: cursorData.y,
+                    width: cursorData.width,
+                    height: cursorData.width,
+                }}>
+                <div className="innerPointer" />
+            </div>
+        );
+    }
+
+    // the previews for each edit
+    function renderEditThumbnails() {
+        return (
+            <div className="editsBox">
+                {edits.map((edit, i) => (
+                    <img
+                        // eslint-disable-next-line react/no-array-index-key
+                        key={i}
+                        alt="image edits"
+                        width={75}
+                        src={edit.url}
+                        onClick={async () => {
+                            const img = new Image();
+                            img.src = edit.url;
+                            ImageUtility.drawImgToCanvas(img, canvasRef, img.width, img.height);
+                            currImg.current = img;
+                            parentDoc.current = edit.saveRes ?? null;
+                        }}
+                    />
+                ))}
+                {/* Original img thumbnail */}
+                {edits.length > 0 && (
+                    <div style={{ position: 'relative' }}>
+                        <label className="originalImageLabel">Original</label>
+                        <img
+                            alt="image stuff"
+                            width={75}
+                            src={originalImg.current?.src}
+                            onClick={() => {
+                                if (!originalImg.current) return;
+                                const img = new Image();
+                                img.src = originalImg.current.src;
+                                ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
+                                currImg.current = img;
+                                if (!parentDoc.current) parentDoc.current = originalDoc.current;
+                            }}
+                        />
+                    </div>
+                )}
+            </div>
+        );
+    }
+
+    // the prompt box for generative fill
+    function renderPromptBox() {
+        return (
+            <div>
+                <TextField
+                    value={input}
+                    onChange={e => setInput(e.target.value)}
+                    disabled={isBrushing}
+                    type="text"
+                    label="Prompt"
+                    placeholder="Prompt..."
+                    InputLabelProps={{ style: { fontSize: '16px' } }}
+                    inputProps={{ style: { fontSize: '16px' } }}
+                    sx={{
+                        backgroundColor: '#ffffff',
+                        position: 'absolute',
+                        bottom: '16px',
+                        transform: 'translateX(calc(50vw - 50%))',
+                        width: 'calc(100vw - 64px)',
+                    }}
+                />
+            </div>
+        );
+    }
+
+    return (
+        <div className="imageEditorContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
+            {renderControls()}
+            {/* Main canvas for editing */}
+            <div
+                className="drawingArea" // this only works if pointerevents: none is set on the custom pointer
+                ref={drawingAreaRef}
+                onPointerOver={updateCursorData}
+                onPointerMove={updateCursorData}
+                onPointerDown={handlePointerDown}
+                onPointerUp={handlePointerUp}>
+                <canvas ref={canvasRef} width={canvasDims.width} height={canvasDims.height} style={{ transform: `scale(${canvasScale})` }} />
+                <canvas ref={canvasBackgroundRef} width={canvasDims.width} height={canvasDims.height} style={{ transform: `scale(${canvasScale})` }} />
+                {renderPointer()}
+                {renderSideIcons()}
+                {renderEditThumbnails()}
+            </div>
+            {currTool.type === ImageToolType.GenerativeFill && renderPromptBox()}
+        </div>
+    );
+};
+
+export default ImageEditor;
diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
new file mode 100644
index 000000000..e90babf9b
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
@@ -0,0 +1,69 @@
+import './GenerativeFillButtons.scss';
+import * as React from 'react';
+import ReactLoading from 'react-loading';
+import { Button, IconButton, Type } from 'browndash-components';
+import { AiOutlineInfo } from 'react-icons/ai';
+import { bgColor } from './imageEditorUtils/imageEditorConstants';
+import { ImageEditTool, ImageToolType } from './imageEditorUtils/imageEditorInterfaces';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { SettingsManager } from '../../../util/SettingsManager';
+
+interface ButtonContainerProps {
+    onClick: () => Promise<void>;
+    loading: boolean;
+    onReset: () => void;
+    btnText: string;
+}
+
+export function ApplyFuncButtons({ loading, onClick: getEdit, onReset, btnText }: ButtonContainerProps) {
+    return (
+        <div className="generativeFillBtnContainer">
+            <Button text="RESET" type={Type.PRIM} color={SettingsManager.userVariantColor} onClick={onReset} />
+            {loading ? (
+                <Button
+                    text={btnText}
+                    type={Type.TERT}
+                    color={SettingsManager.userVariantColor}
+                    icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
+                    iconPlacement="right"
+                    onClick={() => {
+                        if (!loading) getEdit();
+                    }}
+                />
+            ) : (
+                <Button
+                    text={btnText}
+                    type={Type.TERT}
+                    color={SettingsManager.userVariantColor}
+                    onClick={() => {
+                        if (!loading) getEdit();
+                    }}
+                />
+            )}
+            <IconButton
+                type={Type.SEC}
+                color={SettingsManager.userVariantColor}
+                tooltip="Open Documentation"
+                icon={<AiOutlineInfo size="16px" />}
+                onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')}
+            />
+        </div>
+    );
+}
+
+export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTool: (type: ImageToolType) => void) {
+    return (
+        <div className="imageEditorButtonContainer">
+            <Button
+                style={{ width: '100%', border: '1px' }}
+                text={tool.name}
+                type={Type.TERT}
+                color={isActive ? SettingsManager.userVariantColor : bgColor}
+                icon={<FontAwesomeIcon icon={tool.icon} />}
+                onClick={() => {
+                    selectTool(tool.type);
+                }}
+            />
+        </div>
+    );
+}
diff --git a/src/client/views/nodes/imageEditor/imageEditingUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditingUtils/ImageHandler.ts
new file mode 100644
index 000000000..514e8a94f
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageEditingUtils/ImageHandler.ts
@@ -0,0 +1,312 @@
+import { RefObject } from 'react';
+import { bgColor, canvasSize } from '../ImageEditorUtils/imageEditorConstants';
+
+export interface APISuccess {
+    status: 'success';
+    urls: string[];
+}
+
+export interface APIError {
+    status: 'error';
+    message: string;
+}
+
+export class ImageUtility {
+    /**
+     *
+     * @param canvas Canvas to convert
+     * @returns Blob of canvas
+     */
+    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> =>
+        new Promise(resolve => {
+            canvas.toBlob(blob => {
+                if (blob) {
+                    resolve(blob);
+                }
+            }, 'image/png');
+        });
+
+    // 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;
+                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;
+        }
+        return undefined;
+    };
+
+    // converts an image to a canvas data url
+    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> =>
+        new Promise<string>((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;
+        });
+
+    // calls the openai api to get image edits
+    static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
+        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 ${process.env.OPENAI_KEY}`,
+                },
+                body: fd,
+            });
+            const data = await res.json();
+            console.log(data.data);
+            return {
+                status: 'success',
+                urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`),
+            };
+        } catch (err) {
+            console.log(err);
+            return { status: 'error', message: 'API error.' };
+        }
+    };
+
+    // mock api call
+    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({
+        status: 'success',
+        urls: [mockSrc, mockSrc, mockSrc],
+    });
+
+    // Gets the canvas rendering context of a canvas
+    static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
+        if (!canvasRef.current) return null;
+        const ctx = canvasRef.current.getContext('2d');
+        if (!ctx) return null;
+        return ctx;
+    };
+
+    // Helper for downloading the canvas (for debugging)
+    static downloadCanvas = (canvas: HTMLCanvasElement) => {
+        const url = canvas.toDataURL();
+        const downloadLink = document.createElement('a');
+        downloadLink.href = url;
+        downloadLink.download = 'canvas';
+
+        downloadLink.click();
+        downloadLink.remove();
+    };
+
+    // Download the canvas (for debugging)
+    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);
+        };
+    };
+
+    // Clears the canvas
+    static clearCanvas = (canvasRef: React.RefObject<HTMLCanvasElement>) => {
+        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<HTMLCanvasElement>, width: number, height: number) => {
+        const drawImg = (htmlImg: HTMLImageElement) => {
+            const ctx = this.getCanvasContext(canvasRef);
+            if (!ctx) return;
+            ctx.globalCompositeOperation = 'source-over';
+            ctx.clearRect(0, 0, width, height);
+            ctx.drawImage(htmlImg, 0, 0, width, height);
+        };
+
+        if (img.complete) {
+            drawImg(img);
+        } else {
+            img.onload = () => {
+                drawImg(img);
+            };
+        }
+    };
+
+    // Gets the image mask for the openai endpoint
+    static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
+        const canvas = document.createElement('canvas');
+        canvas.width = canvasSize;
+        canvas.height = canvasSize;
+        const ctx = canvas.getContext('2d');
+        if (!ctx) return undefined;
+        ctx?.clearRect(0, 0, canvasSize, canvasSize);
+        ctx.drawImage(paddedCanvas, 0, 0);
+
+        // 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;
+    };
+
+    // 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;
+        for (let i = 0; i < canvas.height; i++) {
+            for (let j = 0; j < xOffset; j++) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceI = i;
+                const sourceJ = xOffset + (xOffset - j);
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        for (let i = 0; i < canvas.height; i++) {
+            for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceI = i;
+                const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j));
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        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;
+        for (let j = 0; j < canvas.width; j++) {
+            for (let i = 0; i < yOffset; i++) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceJ = j;
+                const sourceI = yOffset + (yOffset - i);
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        for (let j = 0; j < canvas.width; j++) {
+            for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceJ = j;
+                const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i));
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        ctx.putImageData(imageData, 0, 0);
+    };
+
+    // 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;
+        canvas.height = canvasSize;
+        const ctx = canvas.getContext('2d');
+        if (!ctx) return undefined;
+        // fix scaling
+        const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
+        const width = Math.floor(img.width * scale);
+        const height = Math.floor(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 = Math.floor((canvasSize - width) / 2);
+            ctx.drawImage(img, xOffset, 0, width, height);
+
+            // draw reflected image padding
+            this.drawHorizontalReflection(ctx, canvas, xOffset);
+        } else {
+            // vertical padding, y offset
+            const yOffset = Math.floor((canvasSize - height) / 2);
+            ctx.drawImage(img, 0, yOffset, width, height);
+
+            // draw reflected image padding
+            this.drawVerticalReflection(ctx, canvas, yOffset);
+        }
+        return canvas;
+    };
+
+    /**
+     * Converts a url to base64 (tainted canvas workaround)
+     */
+    static urlToBase64 = async (imageUrl: string): Promise<string | undefined> => {
+        try {
+            const res = await fetch(imageUrl);
+            const blob = await res.blob();
+
+            return new Promise<string>((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);
+        }
+        return undefined;
+    };
+}
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts
new file mode 100644
index 000000000..a9fe02d4f
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts
@@ -0,0 +1,35 @@
+import { GenerativeFillMathHelpers } from './GenerativeFillMathHelpers';
+import { eraserColor } from './imageEditorConstants';
+import { Point } from './imageEditorInterfaces';
+import { points } from '@turf/turf';
+
+export enum BrushType {
+    GEN_FILL,
+    CUT,
+}
+
+export class BrushHandler {
+    static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => {
+        ctx.globalCompositeOperation = 'destination-out';
+        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 createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, brushType: BrushType) => {
+        const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
+        const pts: Point[] = [];
+        for (let i = 0; i < dist; i += 5) {
+            const s = i / dist;
+            const x = startPoint.x * (1 - s) + endPoint.x * s;
+            const y = startPoint.y * (1 - s) + endPoint.y * s;
+            pts.push({ x: startPoint.x, y: startPoint.y });
+            BrushHandler.brushCircleOverlay(x, y, brushRadius, ctx, fillColor);
+        }
+        return pts;
+    };
+}
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/GenerativeFillMathHelpers.ts
new file mode 100644
index 000000000..f820300f3
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/GenerativeFillMathHelpers.ts
@@ -0,0 +1,6 @@
+import { Point } from './imageEditorInterfaces';
+
+export class GenerativeFillMathHelpers {
+    static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
+    static angleBetween = (p1: Point, p2: Point) => Math.atan2(p2.x - p1.x, p2.y - p1.y);
+}
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
new file mode 100644
index 000000000..ece0f4d7f
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
@@ -0,0 +1,312 @@
+import { RefObject } from 'react';
+import { bgColor, canvasSize } from './imageEditorConstants';
+
+export interface APISuccess {
+    status: 'success';
+    urls: string[];
+}
+
+export interface APIError {
+    status: 'error';
+    message: string;
+}
+
+export class ImageUtility {
+    /**
+     *
+     * @param canvas Canvas to convert
+     * @returns Blob of canvas
+     */
+    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> =>
+        new Promise(resolve => {
+            canvas.toBlob(blob => {
+                if (blob) {
+                    resolve(blob);
+                }
+            }, 'image/png');
+        });
+
+    // 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;
+                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;
+        }
+        return undefined;
+    };
+
+    // converts an image to a canvas data url
+    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> =>
+        new Promise<string>((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;
+        });
+
+    // calls the openai api to get image edits
+    static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
+        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 ${process.env.OPENAI_KEY}`,
+                },
+                body: fd,
+            });
+            const data = await res.json();
+            console.log(data.data);
+            return {
+                status: 'success',
+                urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`),
+            };
+        } catch (err) {
+            console.log(err);
+            return { status: 'error', message: 'API error.' };
+        }
+    };
+
+    // mock api call
+    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({
+        status: 'success',
+        urls: [mockSrc, mockSrc, mockSrc],
+    });
+
+    // Gets the canvas rendering context of a canvas
+    static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
+        if (!canvasRef.current) return null;
+        const ctx = canvasRef.current.getContext('2d');
+        if (!ctx) return null;
+        return ctx;
+    };
+
+    // Helper for downloading the canvas (for debugging)
+    static downloadCanvas = (canvas: HTMLCanvasElement) => {
+        const url = canvas.toDataURL();
+        const downloadLink = document.createElement('a');
+        downloadLink.href = url;
+        downloadLink.download = 'canvas';
+
+        downloadLink.click();
+        downloadLink.remove();
+    };
+
+    // Download the canvas (for debugging)
+    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);
+        };
+    };
+
+    // Clears the canvas
+    static clearCanvas = (canvasRef: React.RefObject<HTMLCanvasElement>) => {
+        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<HTMLCanvasElement>, width: number, height: number) => {
+        const drawImg = (htmlImg: HTMLImageElement) => {
+            const ctx = this.getCanvasContext(canvasRef);
+            if (!ctx) return;
+            ctx.globalCompositeOperation = 'source-over';
+            ctx.clearRect(0, 0, canvasRef.current?.width || width, canvasRef.current?.height || height);
+            ctx.drawImage(htmlImg, 0, 0, width, height);
+        };
+
+        if (img.complete) {
+            drawImg(img);
+        } else {
+            img.onload = () => {
+                drawImg(img);
+            };
+        }
+    };
+
+    // Gets the image mask for the openai endpoint
+    static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
+        const canvas = document.createElement('canvas');
+        canvas.width = canvasSize;
+        canvas.height = canvasSize;
+        const ctx = canvas.getContext('2d');
+        if (!ctx) return undefined;
+        ctx?.clearRect(0, 0, canvasSize, canvasSize);
+        ctx.drawImage(paddedCanvas, 0, 0);
+
+        // 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;
+    };
+
+    // 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;
+        for (let i = 0; i < canvas.height; i++) {
+            for (let j = 0; j < xOffset; j++) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceI = i;
+                const sourceJ = xOffset + (xOffset - j);
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        for (let i = 0; i < canvas.height; i++) {
+            for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceI = i;
+                const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j));
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        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;
+        for (let j = 0; j < canvas.width; j++) {
+            for (let i = 0; i < yOffset; i++) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceJ = j;
+                const sourceI = yOffset + (yOffset - i);
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        for (let j = 0; j < canvas.width; j++) {
+            for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceJ = j;
+                const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i));
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        ctx.putImageData(imageData, 0, 0);
+    };
+
+    // 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;
+        canvas.height = canvasSize;
+        const ctx = canvas.getContext('2d');
+        if (!ctx) return undefined;
+        // fix scaling
+        const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
+        const width = Math.floor(img.width * scale);
+        const height = Math.floor(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 = Math.floor((canvasSize - width) / 2);
+            ctx.drawImage(img, xOffset, 0, width, height);
+
+            // draw reflected image padding
+            this.drawHorizontalReflection(ctx, canvas, xOffset);
+        } else {
+            // vertical padding, y offset
+            const yOffset = Math.floor((canvasSize - height) / 2);
+            ctx.drawImage(img, 0, yOffset, width, height);
+
+            // draw reflected image padding
+            this.drawVerticalReflection(ctx, canvas, yOffset);
+        }
+        return canvas;
+    };
+
+    /**
+     * Converts a url to base64 (tainted canvas workaround)
+     */
+    static urlToBase64 = async (imageUrl: string): Promise<string | undefined> => {
+        try {
+            const res = await fetch(imageUrl);
+            const blob = await res.blob();
+
+            return new Promise<string>((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);
+        }
+        return undefined;
+    };
+}
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/PointerHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/PointerHandler.ts
new file mode 100644
index 000000000..e86f46636
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/PointerHandler.ts
@@ -0,0 +1,11 @@
+import { Point } from './imageEditorInterfaces';
+
+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/imageEditor/imageEditorUtils/imageEditorConstants.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts
new file mode 100644
index 000000000..4772304bc
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts
@@ -0,0 +1,9 @@
+export const canvasSize = 1024;
+export const freeformRenderSize = 300;
+export const offsetDistanceY = freeformRenderSize + 400;
+export const offsetX = 200;
+export const newCollectionSize = 500;
+
+export const activeColor = '#1976d2';
+export const eraserColor = '#e1e9ec';
+export const bgColor = '#f0f4f6';
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
new file mode 100644
index 000000000..f4ae7d9c4
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
@@ -0,0 +1,38 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+
+export interface CursorData {
+    x: number;
+    y: number;
+    width: number;
+}
+
+export interface Point {
+    x: number;
+    y: number;
+}
+
+export enum ImageToolType {
+    GenerativeFill = 'genFill',
+    Cut = 'cut',
+}
+
+export interface ImageEditTool {
+    type: ImageToolType;
+    name: string;
+    btnText: string;
+    icon: IconProp;
+    applyFunc: () => Promise<void>;
+    sliderMin?: number;
+    sliderMax?: number;
+    sliderDefault?: number;
+}
+
+export enum BrushMode {
+    ADD,
+    SUBTRACT,
+}
+
+export interface ImageDimensions {
+    width: number;
+    height: number;
+}
diff --git a/src/client/views/nodes/imageEditor/imageToolUtils/BrushHandler.ts b/src/client/views/nodes/imageEditor/imageToolUtils/BrushHandler.ts
new file mode 100644
index 000000000..7139bebc3
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageToolUtils/BrushHandler.ts
@@ -0,0 +1,35 @@
+import { GenerativeFillMathHelpers } from '../imageEditorUtils/GenerativeFillMathHelpers';
+import { eraserColor } from '../imageEditorUtils/imageEditorConstants';
+import { Point } from '../imageEditorUtils/imageEditorInterfaces';
+import { points } from '@turf/turf';
+
+export enum BrushType {
+    GEN_FILL,
+    CUT,
+}
+
+export class BrushHandler {
+    static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => {
+        ctx.globalCompositeOperation = 'destination-out';
+        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 createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, brushType: BrushType) => {
+        const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
+        const pts: Point[] = [];
+        for (let i = 0; i < dist; i += 5) {
+            const s = i / dist;
+            const x = startPoint.x * (1 - s) + endPoint.x * s;
+            const y = startPoint.y * (1 - s) + endPoint.y * s;
+            pts.push({ x: startPoint.x, y: startPoint.y });
+            BrushHandler.brushCircleOverlay(x, y, brushRadius, ctx, fillColor);
+        }
+        return pts;
+    };
+}
diff --git a/src/client/views/nodes/imageEditor/imageToolUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageToolUtils/ImageHandler.ts
new file mode 100644
index 000000000..b9723b5be
--- /dev/null
+++ b/src/client/views/nodes/imageEditor/imageToolUtils/ImageHandler.ts
@@ -0,0 +1,312 @@
+import { RefObject } from 'react';
+import { bgColor, canvasSize } from '../imageEditorUtils/imageEditorConstants';
+
+export interface APISuccess {
+    status: 'success';
+    urls: string[];
+}
+
+export interface APIError {
+    status: 'error';
+    message: string;
+}
+
+export class ImageUtility {
+    /**
+     *
+     * @param canvas Canvas to convert
+     * @returns Blob of canvas
+     */
+    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> =>
+        new Promise(resolve => {
+            canvas.toBlob(blob => {
+                if (blob) {
+                    resolve(blob);
+                }
+            }, 'image/png');
+        });
+
+    // 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;
+                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;
+        }
+        return undefined;
+    };
+
+    // converts an image to a canvas data url
+    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> =>
+        new Promise<string>((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;
+        });
+
+    // calls the openai api to get image edits
+    static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
+        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 ${process.env.OPENAI_KEY}`,
+                },
+                body: fd,
+            });
+            const data = await res.json();
+            console.log(data.data);
+            return {
+                status: 'success',
+                urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`),
+            };
+        } catch (err) {
+            console.log(err);
+            return { status: 'error', message: 'API error.' };
+        }
+    };
+
+    // mock api call
+    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => ({
+        status: 'success',
+        urls: [mockSrc, mockSrc, mockSrc],
+    });
+
+    // Gets the canvas rendering context of a canvas
+    static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
+        if (!canvasRef.current) return null;
+        const ctx = canvasRef.current.getContext('2d');
+        if (!ctx) return null;
+        return ctx;
+    };
+
+    // Helper for downloading the canvas (for debugging)
+    static downloadCanvas = (canvas: HTMLCanvasElement) => {
+        const url = canvas.toDataURL();
+        const downloadLink = document.createElement('a');
+        downloadLink.href = url;
+        downloadLink.download = 'canvas';
+
+        downloadLink.click();
+        downloadLink.remove();
+    };
+
+    // Download the canvas (for debugging)
+    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);
+        };
+    };
+
+    // Clears the canvas
+    static clearCanvas = (canvasRef: React.RefObject<HTMLCanvasElement>) => {
+        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<HTMLCanvasElement>, width: number, height: number) => {
+        const drawImg = (htmlImg: HTMLImageElement) => {
+            const ctx = this.getCanvasContext(canvasRef);
+            if (!ctx) return;
+            ctx.globalCompositeOperation = 'source-over';
+            ctx.clearRect(0, 0, width, height);
+            ctx.drawImage(htmlImg, 0, 0, width, height);
+        };
+
+        if (img.complete) {
+            drawImg(img);
+        } else {
+            img.onload = () => {
+                drawImg(img);
+            };
+        }
+    };
+
+    // Gets the image mask for the openai endpoint
+    static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
+        const canvas = document.createElement('canvas');
+        canvas.width = canvasSize;
+        canvas.height = canvasSize;
+        const ctx = canvas.getContext('2d');
+        if (!ctx) return undefined;
+        ctx?.clearRect(0, 0, canvasSize, canvasSize);
+        ctx.drawImage(paddedCanvas, 0, 0);
+
+        // 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;
+    };
+
+    // 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;
+        for (let i = 0; i < canvas.height; i++) {
+            for (let j = 0; j < xOffset; j++) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceI = i;
+                const sourceJ = xOffset + (xOffset - j);
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        for (let i = 0; i < canvas.height; i++) {
+            for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceI = i;
+                const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j));
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        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;
+        for (let j = 0; j < canvas.width; j++) {
+            for (let i = 0; i < yOffset; i++) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceJ = j;
+                const sourceI = yOffset + (yOffset - i);
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        for (let j = 0; j < canvas.width; j++) {
+            for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceJ = j;
+                const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i));
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        ctx.putImageData(imageData, 0, 0);
+    };
+
+    // 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;
+        canvas.height = canvasSize;
+        const ctx = canvas.getContext('2d');
+        if (!ctx) return undefined;
+        // fix scaling
+        const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
+        const width = Math.floor(img.width * scale);
+        const height = Math.floor(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 = Math.floor((canvasSize - width) / 2);
+            ctx.drawImage(img, xOffset, 0, width, height);
+
+            // draw reflected image padding
+            this.drawHorizontalReflection(ctx, canvas, xOffset);
+        } else {
+            // vertical padding, y offset
+            const yOffset = Math.floor((canvasSize - height) / 2);
+            ctx.drawImage(img, 0, yOffset, width, height);
+
+            // draw reflected image padding
+            this.drawVerticalReflection(ctx, canvas, yOffset);
+        }
+        return canvas;
+    };
+
+    /**
+     * Converts a url to base64 (tainted canvas workaround)
+     */
+    static urlToBase64 = async (imageUrl: string): Promise<string | undefined> => {
+        try {
+            const res = await fetch(imageUrl);
+            const blob = await res.blob();
+
+            return new Promise<string>((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);
+        }
+        return undefined;
+    };
+}
diff --git a/src/client/views/smartdraw/AnnotationPalette.tsx b/src/client/views/smartdraw/AnnotationPalette.tsx
index f1e2e4f41..dc0c01d36 100644
--- a/src/client/views/smartdraw/AnnotationPalette.tsx
+++ b/src/client/views/smartdraw/AnnotationPalette.tsx
@@ -185,7 +185,7 @@ export class AnnotationPalette extends ObservableReactComponent<AnnotationPalett
         docData.drawingColored = this._opts.autoColor;
         docData.drawingSize = this._opts.size;
         docData.drawingData = this._gptRes[cIndex];
-        docData.width = this._opts.size;
+        focusedDrawing.width = this._opts.size;
         docData.x = this._opts.x;
         docData.y = this._opts.y;
         await AnnotationPalette.addToPalette(focusedDrawing);
-- 
cgit v1.2.3-70-g09d2


From 89424e0a8efc6cf3364a2fd1ffc85c9d0d837453 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 22 Nov 2024 10:27:33 -0500
Subject: added initial Firefly endpoint and hanged smartDrawHandler to
 generate an image and an svg.

---
 src/client/util/bezierFit.ts                       |  3 +-
 src/client/views/MainView.tsx                      | 32 ++++++++++++--
 .../nodes/chatbot/chatboxcomponents/ChatBox.tsx    |  3 +-
 src/client/views/pdf/AnchorMenu.tsx                |  9 ++--
 src/client/views/smartdraw/SmartDrawHandler.tsx    | 29 ++++++------
 src/server/ApiManagers/DataVizManager.ts           |  2 +-
 src/server/ApiManagers/FireflyManager.ts           | 51 ++++++++++++++++++++++
 src/server/DashUploadUtils.ts                      |  3 +-
 src/server/index.ts                                |  3 +-
 webpack.config.js                                  |  7 ++-
 10 files changed, 113 insertions(+), 29 deletions(-)
 create mode 100644 src/server/ApiManagers/FireflyManager.ts

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index d52460023..84b27e84c 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -703,7 +703,6 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
                     coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) });
                     coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) });
                     coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) });
-                    coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) });
                     lastPt = { X: parseInt(match[3]), Y: parseInt(match[4]) };
                 } else if (match[0].startsWith('C')) {
                     coordList.push({ X: parseInt(match[5]), Y: parseInt(match[6]) });
@@ -720,7 +719,7 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] {
                 }
             });
             const hasZ = attributes.d.match(/Z/);
-            if (hasZ) {
+            if (hasZ || attributes.fill) {
                 coordList.push(lastPt);
                 coordList.push(startPt);
                 coordList.push(startPt);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7779d339f..0d071fe4f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -8,7 +8,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import ResizeObserver from 'resize-observer-polyfill';
 import '../../../node_modules/browndash-components/dist/styles/global.min.css';
-import { ClientUtils, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
+import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, GetDocFromUrl, Opt, returnEmptyDoclist } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
@@ -1023,10 +1023,36 @@ export class MainView extends ObservableReactComponent<object> {
                 <svg style={{ width: '100%', height: '100%' }}>
                     {[
                         ...SnappingManager.HorizSnapLines.map(l => (
-                            <line key={'horiz' + l} x1="0" y1={l} x2="2000" y2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+                            <line
+                                key={'horiz' + l}
+                                x1="0"
+                                y1={l}
+                                x2="2000"
+                                y2={l}
+                                stroke={
+                                    SnappingManager.userVariantColor
+                                    /* lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))*/
+                                }
+                                opacity={0.3}
+                                strokeWidth={3}
+                                strokeDasharray="2 2"
+                            />
                         )),
                         ...SnappingManager.VertSnapLines.map(l => (
-                            <line key={'vert' + l} y1={this.topOfMainDocContent.toString()} x1={l} y2="2000" x2={l} stroke={lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))} opacity={0.3} strokeWidth={1} strokeDasharray="2 2" />
+                            <line
+                                key={'vert' + l}
+                                y1={this.topOfMainDocContent.toString()}
+                                x1={l}
+                                y2="2000"
+                                x2={l}
+                                stroke={
+                                    SnappingManager.userVariantColor
+                                    /* lightOrDark(StrCast(dragPar.layoutDoc.backgroundColor, 'gray'))*/
+                                }
+                                opacity={0.3}
+                                strokeWidth={3}
+                                strokeDasharray="2 2"
+                            />
                         )),
                     ]}
                 </svg>
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index a61705250..3ef6bdd8b 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -431,7 +431,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 doc = Docs.Create.FunctionPlotDocument([], options);
                 break;
             case 'dataviz':
-            case 'data_viz':
+            case 'data_viz': {
                 const { fileUrl, id } = await Networking.PostToServer('/createCSV', {
                     filename: (options.title as string).replace(/\s+/g, '') + '.csv',
                     data: data,
@@ -439,6 +439,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 doc = Docs.Create.DataVizDocument(fileUrl, { ...options, text: RTFCast(data) });
                 this.addCSVForAnalysis(doc, id);
                 break;
+            }
             case 'chat':
                 doc = Docs.Create.ChatDocument(options);
                 break;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 5ab9b556c..fe03f32a5 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -131,12 +131,15 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     /**
      * Creates a GPT drawing based on selected text.
      */
-    gptDraw = async (e: React.PointerEvent) => {
+    gptDraw = (e: React.PointerEvent) => {
         try {
             SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation;
             runInAction(() => (this._isLoading = true));
-            await SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true);
-            runInAction(() => (this._isLoading = false));
+            SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true)?.then(
+                action(() => {
+                    this._isLoading = false;
+                })
+            );
         } catch (err) {
             console.error(err);
         }
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index d0f6566a5..342b91bd9 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -13,6 +13,7 @@ import { Doc, DocListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { InkData, InkField, InkTool } from '../../../fields/InkField';
 import { BoolCast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { Networking } from '../../Network';
 import { GPTCallType, gptAPICall, gptDrawingColor } from '../../apis/gpt/GPT';
 import { Docs } from '../../documents/Documents';
 import { SettingsManager } from '../../util/SettingsManager';
@@ -21,7 +22,8 @@ import { SVGToBezier, SVGType } from '../../util/bezierFit';
 import { InkingStroke } from '../InkingStroke';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { MarqueeView } from '../collections/collectionFreeForm';
-import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkDash, ActiveInkFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView';
+import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkBezierApprox, ActiveInkColor, ActiveInkDash, ActiveInkFillColor, ActiveInkWidth, ActiveIsInkMask, DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+import { OpenWhere } from '../nodes/OpenWhere';
 import './SmartDrawHandler.scss';
 
 export interface DrawingOptions {
@@ -230,20 +232,21 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
      * Calls GPT API to create a drawing based on user input.
      */
     @action
-    drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => {
-        if (input === '') return;
-        this._lastInput = { text: input, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y };
-        const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true);
-        if (!res) {
-            console.error('GPT call failed');
-            return;
-        }
-        const strokeData = await this.parseSvg(res, startPt, false, autoColor);
-        const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
-        drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
+    drawWithGPT = (startPt: { X: number; Y: number }, prompt: string, complexity: number, size: number, autoColor: boolean) => {
+        if (prompt === '') return;
+        this._lastInput = { text: prompt, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y };
+
+        Networking.PostToServer('/queryFireflyImage', { prompt }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, { title: prompt }), OpenWhere.addRight));
+
+        const result = gptAPICall(`"${prompt}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true).then(res =>
+            this.parseSvg(res, startPt, false, autoColor).then(strokeData => {
+                const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
+                drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
+            })
+        );
 
         this._errorOccurredOnce = false;
-        return strokeData;
+        return result;
     };
 
     /**
diff --git a/src/server/ApiManagers/DataVizManager.ts b/src/server/ApiManagers/DataVizManager.ts
index 88f22992d..d2028f23b 100644
--- a/src/server/ApiManagers/DataVizManager.ts
+++ b/src/server/ApiManagers/DataVizManager.ts
@@ -9,7 +9,7 @@ export default class DataVizManager extends ApiManager {
         register({
             method: Method.GET,
             subscription: '/csvData',
-            secureHandler: async ({ req, res }) => {
+            secureHandler: ({ req, res }) => {
                 const uri = req.query.uri as string;
 
                 return new Promise<void>(resolve => {
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
new file mode 100644
index 000000000..04fa8f065
--- /dev/null
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -0,0 +1,51 @@
+import { DashUploadUtils } from '../DashUploadUtils';
+import { _invalid, _success, Method } from '../RouteManager';
+import ApiManager, { Registration } from './ApiManager';
+
+export default class FireflyManager extends ApiManager {
+    askFirefly = (prompt: string = 'a realistic illustration of a cat coding') => {
+        const fetched = fetch('https://ims-na1.adobelogin.com/ims/token/v3', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+            },
+            body: `grant_type=client_credentials&client_id=${process.env._CLIENT_FIREFLY_CLIENT_ID}&client_secret=${process.env._CLIENT_FIREFLY_SECRET}&scope=openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis`,
+        })
+            .then(response => response.json())
+            .then((data: { access_token: string }) =>
+                fetch('https://firefly-api.adobe.io/v3/images/generate', {
+                    method: 'POST',
+                    headers: [
+                        ['Content-Type', 'application/json'],
+                        ['Accept', 'application/json'],
+                        ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''],
+                        ['Authorization', `Bearer ${data.access_token}`],
+                    ],
+                    body: `{  "prompt": "${prompt}" }`,
+                })
+                    .then(response => response.json().then(json => JSON.stringify((json.outputs?.[0] as { image: { url: string } })?.image)))
+                    .catch(error => {
+                        console.error('Error:', error);
+                        return '';
+                    })
+            )
+            .catch(error => {
+                console.error('Error:', error);
+                return '';
+            });
+        return fetched;
+    };
+    protected initialize(register: Registration): void {
+        register({
+            method: Method.POST,
+            subscription: '/queryFireflyImage',
+            secureHandler: ({ req, res }) =>
+                this.askFirefly(req.body.prompt).then(fire =>
+                    DashUploadUtils.UploadImage(JSON.parse(fire).url).then(info => {
+                        if (info instanceof Error) _invalid(res, info.message);
+                        else _success(res, info.accessPaths.agnostic.client);
+                    })
+                ),
+        });
+    }
+}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 1e55a885a..032d13d43 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -369,7 +369,8 @@ export namespace DashUploadUtils {
      */
     export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename: string, prefix = '', cleanUp = true): Promise<Upload.ImageInformation> => {
         const { requestable, source, ...remaining } = metadata;
-        const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split('/')[1].toLowerCase()}`;
+        const dfltSuffix = remaining.contentType.split('/')[1].toLowerCase();
+        const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${dfltSuffix === 'xml' ? 'jpg' : dfltSuffix}`;
         const { images } = Directory;
         const information: Upload.ImageInformation = {
             accessPaths: {
diff --git a/src/server/index.ts b/src/server/index.ts
index 88dbd232d..1f9af9ee0 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -7,6 +7,7 @@ import AssistantManager from './ApiManagers/AssistantManager';
 import DataVizManager from './ApiManagers/DataVizManager';
 import DeleteManager from './ApiManagers/DeleteManager';
 import DownloadManager from './ApiManagers/DownloadManager';
+import FireflyManager from './ApiManagers/FireflyManager';
 import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager';
 import SessionManager from './ApiManagers/SessionManager';
 import UploadManager from './ApiManagers/UploadManager';
@@ -71,6 +72,7 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage
         new GeneralGoogleManager(),
         /* new GooglePhotosManager(), */ new DataVizManager(),
         new AssistantManager(),
+        new FireflyManager(),
     ];
 
     // initialize API Managers
@@ -112,7 +114,6 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage
     });
 
     const serve: PublicHandler = ({ req, res }) => {
-        // eslint-disable-next-line new-cap
         const detector = new mobileDetect(req.headers['user-agent'] || '');
         const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
         res.sendFile(path.join(__dirname, '../../deploy/' + filename));
diff --git a/webpack.config.js b/webpack.config.js
index e1afc64e5..67417fb02 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,4 +1,4 @@
-/* eslint-disable @typescript-eslint/no-var-requires */
+/* eslint-disable @typescript-eslint/no-require-imports */
 const path = require('path');
 const webpack = require('webpack');
 const HtmlWebpackPlugin = require('html-webpack-plugin');
@@ -36,7 +36,6 @@ function transferEnvironmentVariables() {
     }
     const resolvedClientSide = Object.keys(parsed).reduce((mapping, envKey) => {
         if (envKey.startsWith(prefix)) {
-            // eslint-disable-next-line
             mapping[`process.env.${envKey.replace(prefix, '')}`] = JSON.stringify(parsed[envKey]);
         }
         return mapping;
@@ -112,7 +111,7 @@ module.exports = {
                 test: /\.scss|css$/,
                 exclude: /\.module\.scss$/i,
                 use: [
-                    { loader: 'style-loader' }, // eslint-disable-next-line prettier/prettier
+                    { loader: 'style-loader' }, //
                     { loader: 'css-loader' },
                     { loader: 'sass-loader' },
                 ],
@@ -127,7 +126,7 @@ module.exports = {
             {
                 test: /\.module\.scss$/i,
                 use: [
-                    { loader: 'style-loader' }, // eslint-disable-next-line prettier/prettier
+                    { loader: 'style-loader' }, //
                     { loader: 'css-loader', options: { modules: true } },
                     { loader: 'sass-loader' },
                 ],
-- 
cgit v1.2.3-70-g09d2


From cffb72e762d989eb1219a5736f082f5b656bee37 Mon Sep 17 00:00:00 2001
From: Geireann Lindfield Roberts <geireann_lindfield_roberts@brown.edu>
Date: Wed, 1 Jan 2025 22:45:48 -0800
Subject: Update MainView.tsx

---
 src/client/views/MainView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 0d071fe4f..65d90a5a0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -7,7 +7,7 @@ import { action, computed, configure, makeObservable, observable, reaction, runI
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ResizeObserver from 'resize-observer-polyfill';
-import '../../../node_modules/browndash-components/dist/styles/global.min.css';
+import '@dash/components/src/global/globalCssVariables.scss';
 import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
 import { Doc, DocListCast, GetDocFromUrl, Opt, returnEmptyDoclist } from '../../fields/Doc';
-- 
cgit v1.2.3-70-g09d2


From 6fa61343a3e63a61747bc7123810190057d0bfe4 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 29 Jan 2025 12:14:12 -0500
Subject: cleaned up filter icons.

---
 src/client/util/CurrentUserUtils.ts                | 38 +++++++++++++++++-----
 src/client/views/MainView.tsx                      | 25 +++-----------
 .../views/collections/CollectionCardDeckView.tsx   | 20 +++---------
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 34 +++++++------------
 4 files changed, 48 insertions(+), 69 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index e806675aa..306914450 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -40,6 +40,7 @@ import { ColorScheme } from "./SettingsManager";
 import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
 import { DocumentView } from "../views/nodes/DocumentView";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
 
 export interface Button {
     // DocumentOptions fields a button can set
@@ -67,6 +68,21 @@ export interface Button {
     subMenu?: Button[];
 }
 
+// Not really necessary, but for now, all tags should start with a capital first letter
+export type TagName<T extends string> =
+        // eslint-disable-next-line @typescript-eslint/no-unused-vars
+        T extends `${infer First}${infer Rest}`
+          ? First extends Uppercase<First> 
+            ? First extends Lowercase<First> 
+              ? never  // If it's the same when uppercased and lowercased, it's not a letter.
+              : T      // Otherwise, it's a valid capitalized string.
+            : never
+          : never;
+export function ToTagName(key: string):"Tag"{
+    return ((str => str[0].toUpperCase() + str.slice(1))(key.startsWith('#') ? key.substring(1) : key)) as "Tag";
+}
+
+
 export let resolvedPorts: { server: number, socket: number };
 export class CurrentUserUtils {
 
@@ -703,18 +719,22 @@ pie title Minerals in my tap water
             ]},
          ]
     }
+
+    static filterBtnDesc<T extends string>(tag:TagName<T>|"Tag", icon:IconProp):Button {
+        return { title: tag, isSystem: false, icon: icon.toString(),  toolTip:`Click to toggle visibility of ${tag} tagged Docs`,  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:`#${tag.toLowerCase()}`, funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}
+    }
   
     static filterTools(): Button[] {
-        const defaultTagButtonDescs = [
-            { title: "Star", isSystem: false,icon: "star",  toolTip:"Click to toggle visibility of Star tagged Docs",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"#star", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}},
-            { title: "Like", isSystem: false,icon: "heart", toolTip:"Click to toggle visibility of Like tagged Docs",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"#like", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}},
-            { title: "Todo", isSystem: false,icon: "bolt",  toolTip:"Click to toggle visibility of Todo tagged Docs",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"#todo", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}},
-            { title: "Idea", isSystem: false,icon: "cloud", toolTip:"Click to toggle visibility of Idea tagged Docs",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"#idea", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}},
+        // If there's no active dashboard, then a default set of tags are added, otherwise, the user controls which tags are kept
+        const tagButtonDescs = Doc.UserDoc().activeDashboard ? [] : [
+            this.filterBtnDesc("Star", "star"),
+            this.filterBtnDesc("Like", "heart"),
+            this.filterBtnDesc("Todo", "bolt"),
+            this.filterBtnDesc("Idea", "cloud")
         ];
-        // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
         return [
-            { title:"Options",isSystem: true,icon: "gear",  toolTip:"Click to customize list of filter buttons",       btnType: ButtonType.ClickButton,  expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}},
-            ...(Doc.UserDoc().activeDashboard ? [] : defaultTagButtonDescs)
+            { title:"Options",isSystem: true,icon: "gear",  toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}},
+            ...tagButtonDescs
         ]
     }
     static viewTools(): Button[] {
@@ -866,7 +886,7 @@ pie title Minerals in my tap water
             childDontRegisterViews: true, flexGap: 0, _height: 30, _width: 30, ignoreClick: !params.scripts?.onClick,
             linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
         
-        const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) );
+        const items = (menuBtn?:Doc) => !menuBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtn) );
         const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
         const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title), 
                 (opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7abca5197..d748b70ae 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -20,7 +20,7 @@ import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { Docs } from '../documents/Documents';
 import { CalendarManager } from '../util/CalendarManager';
 import { CaptureManager } from '../util/CaptureManager';
-import { Button, CurrentUserUtils } from '../util/CurrentUserUtils';
+import { CurrentUserUtils, ToTagName } from '../util/CurrentUserUtils';
 import { DocumentManager } from '../util/DocumentManager';
 import { DragManager } from '../util/DragManager';
 import { dropActionType } from '../util/DropActionTypes';
@@ -63,7 +63,6 @@ import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu';
 import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
 import { DocButtonState } from './nodes/DocumentLinksButton';
 import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
-import { ButtonType } from './nodes/FontIconBox/FontIconBox';
 import { ImageEditorData as ImageEditor } from './nodes/ImageBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview';
@@ -874,25 +873,9 @@ export class MainView extends ObservableReactComponent<object> {
      * @param hotKey tite of the new hotkey
      */
     addHotKey = (hotKey: string) => {
-        const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-        const filter = DocCast(buttons.Filter);
-        const title = hotKey.startsWith('#') ? hotKey.substring(1) : hotKey;
-
-        const newKey: Button = {
-            title,
-            icon: 'question',
-            toolTip: `Click to toggle the ${title}'s group's visibility`,
-            btnType: ButtonType.ToggleButton,
-            expertMode: false,
-            toolType: '#' + title,
-            funcs: {},
-            scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' },
-        };
-
-        const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter);
-        newBtn.isSystem = newBtn[DocData].isSystem = undefined;
-
-        Doc.AddToFilterHotKeys(newBtn);
+        const filterIcons = DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter);
+        const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons);
+        Doc.AddToFilterHotKeys(menuDoc);
     };
 
     @computed get mainInnerContent() {
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 366d0f448..b40aa37de 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -462,24 +462,13 @@ export class CollectionCardView extends CollectionSubView() {
                     case '6':
                         doc.chatIndex = index;
                         break;
-                    case '1': {
-                        const allHotKeys = Doc.MyFilterHotKeys;
-                        let myTag = '';
+                    case '1':
                         if (tag) {
-                            for (let i = 0; i < allHotKeys.length; i++) {
-                                const keyTag = StrCast(allHotKeys[i].toolType);
-                                if (keyTag.includes(tag)) {
-                                    myTag = keyTag;
-                                    break;
-                                }
-                            }
-
-                            if (myTag) {
-                                TagItem.addTagToDoc(doc, myTag);
-                            }
+                            const hashTag = tag.startsWith('#') ? tag : '#' + tag[0].toLowerCase() + tag.slice(1);
+                            const filterTag = Doc.MyFilterHotKeys.map(key => StrCast(key.toolType)).find(key => key.includes(tag)) ?? hashTag;
+                            TagItem.addTagToDoc(doc, filterTag);
                         }
                         break;
-                    }
                     case '2':
                     case '4':
                         doc.chatFilter = true;
@@ -584,7 +573,6 @@ export class CollectionCardView extends CollectionSubView() {
      * Actually renders all the cards
      */
     @computed get renderCards() {
-        console.log(this.docDraggedIndex, this.childDocs[0].title, this.childDocs[1].title);
         // Map sorted documents to their rendered components
         return this.childDocs.map((doc, index) => {
             const cardsInRow = this.cardsInRowThatIncludesCardIndex(index);
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 4028ef479..c33b81eb4 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -188,8 +188,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
 
         const selected = DocumentView.SelectedDocs().lastElement();
 
-        const questionText = 'Question: ' + StrCast(selected['gptInputText']);
-        const rubricText = 'Rubric: ' + StrCast(selected['gptRubric']);
+        const questionText = 'Question: ' + StrCast(selected.gptInputText);
+        const rubricText = 'Rubric: ' + StrCast(selected.gptRubric);
         const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText;
 
         try {
@@ -262,27 +262,15 @@ export class GPTPopup extends ObservableReactComponent<object> {
 
         try {
             const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE);
-            const questionNumber = questionType.split(' ')[0];
-            let res = '';
-
-            switch (questionNumber) {
-                case '1':
-                case '2':
-                case '4':
-                    res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt);
-                    break;
-                case '6':
-                    res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
-                    break;
-                default:
-                    {
-                        const selected = DocumentView.SelectedDocs().lastElement();
-                        const questionText = StrCast(selected?.gptInputText);
-
-                        res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt);
-                    }
-                    break;
-            }
+            const questionNumber = questionType.split(' ')[0][0];
+            const res = await (() => {
+                switch (questionNumber) {
+                    case '1':
+                    case '2':
+                    case '4': return gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt);
+                    case '6': return gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt);
+                    default: return gptAPICall(StrCast(DocumentView.SelectedDocs().lastElement()?.gptInputText), GPTCallType.INFO, this.chatSortPrompt);
+                }})(); // prettier-ignore
 
             // Trigger the callback with the result
             if (this.onSortComplete) {
-- 
cgit v1.2.3-70-g09d2


From cf791104c0b1608e37c3cf2d25dac7f6f58a1b66 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 18 Feb 2025 09:01:44 -0500
Subject: css fixes for gptPopup + close button

---
 src/client/util/SnappingManager.ts                 |  3 +
 src/client/views/MainView.tsx                      |  3 +-
 src/client/views/OverlayView.scss                  | 13 ++--
 src/client/views/OverlayView.tsx                   | 18 ++++--
 src/client/views/ScriptBox.tsx                     |  1 -
 src/client/views/global/globalScripts.ts           |  8 +--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 +-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        | 69 ++++++++++------------
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 62 ++++++++++---------
 9 files changed, 98 insertions(+), 83 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 2a150dc5a..9d8a41844 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -32,6 +32,7 @@ export class SnappingManager {
     @observable _hideDecorations: boolean = false;
     @observable _keepGestureMode: boolean = false; // for whether primitive selection enters a one-shot or persistent mode
     @observable _inkShape: Gestures | undefined = undefined;
+    @observable _chatVisible: boolean = false;
 
     private constructor() {
         SnappingManager._manager = this;
@@ -66,6 +67,7 @@ export class SnappingManager {
     public static get HideDecorations(){ return this.Instance._hideDecorations; } // prettier-ignore
     public static get KeepGestureMode(){ return this.Instance._keepGestureMode; } // prettier-ignore
     public static get InkShape()       { return this.Instance._inkShape; } // prettier-ignore
+    public static get ChatVisible()    { return this.Instance._chatVisible; } // prettier-ignore
 
     public static SetLongPress      = (press: boolean) => runInAction(() => {this.Instance._longPress = press}); // prettier-ignore
     public static SetShiftKey       = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
@@ -85,6 +87,7 @@ export class SnappingManager {
     public static SetHideDecorations= (state:boolean)  =>runInAction(() => {this.Instance._hideDecorations = state}); // prettier-ignore
     public static SetKeepGestureMode= (state:boolean)  =>runInAction(() => {this.Instance._keepGestureMode = state}); // prettier-ignore
     public static SetInkShape       = (shape?:Gestures)=>runInAction(() => {this.Instance._inkShape = shape}); // prettier-ignore
+    public static SetChatVisible    = (vis:boolean)    =>runInAction(() => {this.Instance._chatVisible = vis}); // prettier-ignore
 
     public static userColor: string | undefined;
     public static userVariantColor: string | undefined;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d748b70ae..195b1c572 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -78,6 +78,7 @@ import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { SmartDrawHandler } from './smartdraw/SmartDrawHandler';
 import { TopBar } from './topbar/TopBar';
+import { OverlayView } from './OverlayView';
 
 // eslint-disable-next-line @typescript-eslint/no-require-imports
 const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
@@ -168,6 +169,7 @@ export class MainView extends ObservableReactComponent<object> {
     mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
 
     componentDidMount() {
+        OverlayView.Instance.addWindow(<GPTPopup />, { x: 400, y: 200, width: 500, height: 400, title: 'GPT', backgroundColor: 'transparent', isHidden: () => !SnappingManager.ChatVisible, onClick: () => SnappingManager.SetChatVisible(false) });
         // Utils.TraceConsoleLog();
         reaction(
             // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
@@ -1154,7 +1156,6 @@ export class MainView extends ObservableReactComponent<object> {
                 <InkTranscription />
                 {this.snapLines}
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} addSplit={CollectionDockingView.AddSplit} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} />
-                <GPTPopup key="gptpopup" />
                 <SchemaCSVPopUp key="schemacsvpopup" />
                 <ImageEditorBox imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} />
             </div>
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 33a297fd4..f4998efa1 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -4,7 +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
+    z-index: 2002; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear
     /* background-color: pink; */
     user-select: none;
 }
@@ -26,27 +26,30 @@
 }
 
 .overlayWindow-titleBar {
-    flex: 0 1 30px;
+    flex: 0 1 20px;
     background: darkslategray;
     color: whitesmoke;
     text-align: center;
     cursor: move;
+    z-index: 1;
 }
 
 .overlayWindow-content {
     flex: 1 1 auto;
     display: flex;
     flex-direction: column;
+    z-index: 0;
 }
 
 .overlayWindow-closeButton {
     float: right;
-    height: 30px;
-    width: 30px;
+    height: 20px;
+    width: 20px;
+    padding: 0;
+    background-color: inherit;
 }
 
 .overlayWindow-resizeDragger {
-    background-color: rgb(0, 0, 0);
     position: absolute;
     right: 0px;
     bottom: 0px;
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 5e9677b45..20931fc3d 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent';
 import './OverlayView.scss';
 import { DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider';
 import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
+import { SnappingManager } from '../util/SnappingManager';
 
 export type OverlayDisposer = () => void;
 
@@ -27,12 +28,17 @@ export type OverlayElementOptions = {
     width?: number;
     height?: number;
     title?: string;
+    onClick?: (e: React.MouseEvent) => void;
+    isHidden?: () => boolean;
+    backgroundColor?: string;
 };
 
 export interface OverlayWindowProps {
     children: JSX.Element;
     overlayOptions: OverlayElementOptions;
-    onClick: () => void;
+    onClick: (e: React.MouseEvent) => void;
+    isHidden?: () => boolean;
+    backgroundColor?: string;
 }
 
 @observer
@@ -93,15 +99,17 @@ export class OverlayWindow extends ObservableReactComponent<OverlayWindowProps>
 
     render() {
         return (
-            <div className="overlayWindow-outerDiv" style={{ transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
-                <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown}>
+            <div
+                className="overlayWindow-outerDiv"
+                style={{ display: this.props.isHidden?.() ? 'none' : undefined, backgroundColor: this._props.backgroundColor, transform: `translate(${this.x}px, ${this.y}px)`, width: this.width, height: this.height }}>
+                <div className="overlayWindow-titleBar" onPointerDown={this.onPointerDown} style={{ backgroundColor: SnappingManager.userVariantColor, color: SnappingManager.userColor }}>
                     {this._props.overlayOptions.title || 'Untitled'}
                     <button type="button" onClick={this._props.onClick} className="overlayWindow-closeButton">
                         X
                     </button>
                 </div>
                 <div className="overlayWindow-content">{this.props.children}</div>
-                <div className="overlayWindow-resizeDragger" onPointerDown={this.onResizerPointerDown} />
+                <div className="overlayWindow-resizeDragger" style={{ backgroundColor: SnappingManager.userVariantColor }} onPointerDown={this.onResizerPointerDown} />
             </div>
         );
     }
@@ -166,7 +174,7 @@ export class OverlayView extends ObservableReactComponent<object> {
             if (index !== -1) this._elements.splice(index, 1);
         });
         const wincontents = (
-            <OverlayWindow onClick={() => remove(wincontents)} key={Utils.GenerateGuid()} overlayOptions={options}>
+            <OverlayWindow isHidden={options.isHidden} backgroundColor={options.backgroundColor} onClick={options.onClick ?? (() => remove(wincontents))} key={Utils.GenerateGuid()} overlayOptions={options}>
                 {contents}
             </OverlayWindow>
         );
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 9c36e6d26..d05b0a6b6 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/require-default-props */
 import { action, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 790ebeabe..2bc0e3338 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -223,13 +223,13 @@ ScriptingGlobals.add(function showFreeform(
             setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = true; },
         }],
         ['toggle-chat', {
-            checkResult: (doc: Doc) => GPTPopup.Instance.Visible,
+            checkResult: (doc: Doc) => SnappingManager.ChatVisible,
             setDoc: (doc: Doc, dv: DocumentView) => {
-                if (GPTPopup.Instance.Visible){
+                if (SnappingManager.ChatVisible){
                     doc[Doc.LayoutFieldKey(doc)+"_sort"] = '';
-                    GPTPopup.Instance.setVisible(false);
+                    SnappingManager.SetChatVisible(false);
                 } else {
-                    GPTPopup.Instance.setVisible(true);
+                    SnappingManager.SetChatVisible(true);
                     GPTPopup.Instance.setMode(GPTPopupMode.GPT_MENU);
                 }
             },
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 6960247e9..3abb39ff2 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -977,7 +977,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             },
             icon: 'star',
         });
-        optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
+        optionItems.push({ description: `Generate Dall-E Image`, event: this.generateImage, icon: 'star' });
         // optionItems.push({ description: `Make AI Flashcards`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' });
         optionItems.push({ description: `Ask GPT-3`, event: this.askGPT, icon: 'lightbulb' });
         this._props.renderDepth &&
@@ -1061,7 +1061,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
         }
     });
 
-    generateImage = async () => {
+    generateImage = () => {
         GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
         GPTPopup.Instance.generateImage((this.dataDoc.text as RichTextField)?.Text, this.Document, this._props.addDocument);
     };
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 9cf318dc0..0b832f64c 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -4,19 +4,23 @@ $greyborder: #d3d3d3;
 $lightgrey: #ececec;
 $button: #5b97ff;
 $highlightedText: #82e0ff;
+$inputHeight: 60px;
+$headingHeight: 32px;
 
 .gptPopup-summary-box {
     position: fixed;
     top: 115px;
     left: 75px;
-    width: 250px;
-    height: 200px;
-    min-height: 200px;
-    min-width: 180px;
-
+    width: 100%;
+    height: 100%;
+    top: 0;
+    left: 0;
+    pointer-events: none;
+    border-top: solid gray 20px;
     border-radius: 16px;
     padding: 16px;
     padding-bottom: 0;
+    padding-top: 0px;
     z-index: 999;
     display: flex;
     flex-direction: column;
@@ -24,17 +28,12 @@ $highlightedText: #82e0ff;
     background-color: #ffffff;
     box-shadow: 0 2px 5px #7474748d;
     color: $textgrey;
-    resize: both; /* Allows resizing */
-    overflow: auto;
-
-    .resize-handle {
-        width: 10px;
-        height: 10px;
-        background: #ccc;
-        position: absolute;
-        right: 0;
-        bottom: 0;
-        cursor: se-resize;
+
+    .gptPopup-sortBox {
+        display: flex;
+        flex-direction: column;
+        height: calc(100% - $inputHeight - $headingHeight);
+        pointer-events: all;
     }
 
     .summary-heading {
@@ -42,7 +41,7 @@ $highlightedText: #82e0ff;
         justify-content: space-between;
         align-items: center;
         border-bottom: 1px solid $greyborder;
-        padding-bottom: 5px;
+        height: $headingHeight;
 
         .summary-text {
             font-size: 12px;
@@ -66,28 +65,17 @@ $highlightedText: #82e0ff;
     .gptPopup-content-wrapper {
         padding-top: 10px;
         min-height: 50px;
-        // max-height: 150px;
-        overflow-y: auto;
-        height: 100%;
+        height: calc(100% - 32px);
     }
 
-    .btns-wrapper-gpt {
-        height: 100%;
+    .inputWrapper {
         display: flex;
         justify-content: center;
         align-items: center;
-        flex-direction: column;
-
-        .inputWrapper {
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            height: 60px;
-            position: absolute;
-            bottom: 0;
-            width: 100%;
-            background-color: white;
-        }
+        height: $inputHeight;
+        background-color: white;
+        width: 100%;
+        pointer-events: all;
 
         .searchBox-input {
             height: 40px;
@@ -97,14 +85,21 @@ $highlightedText: #82e0ff;
             border-color: #5b97ff;
             width: 90%;
         }
+    }
+    .btns-wrapper-gpt {
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        flex-direction: column;
 
         .chat-wrapper {
             display: flex;
             flex-direction: column;
             width: 100%;
-            max-height: calc(100vh - 80px);
+            height: 100%;
             overflow-y: auto;
-            padding-bottom: 60px;
+            padding-right: 5px;
         }
 
         .chat-bubbles {
@@ -194,7 +189,7 @@ $highlightedText: #82e0ff;
 .image-content-wrapper {
     display: flex;
     flex-direction: column;
-    align-items: flex-start;
+    align-items: center;
     gap: 8px;
     padding-bottom: 16px;
 
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index f09d786d0..72381cfad 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { CgClose, CgCornerUpLeft } from 'react-icons/cg';
+import { CgCornerUpLeft } from 'react-icons/cg';
 import ReactLoading from 'react-loading';
 import { TypeAnimation } from 'react-type-animation';
 import { ClientUtils } from '../../../../ClientUtils';
@@ -108,8 +108,6 @@ export class GPTPopup extends ObservableReactComponent<object> {
 
     @observable private _mode: GPTPopupMode = GPTPopupMode.SUMMARY;
     @action public setMode = (mode: GPTPopupMode) => (this._mode = mode);
-    @observable public Visible: boolean = false;
-    @action public setVisible = (vis: boolean) => (this.Visible = vis);
 
     onQuizRandom?: () => void;
     onGptResponse?: (sortResult: string, questionType: GPTTypeStyle, tag?: string) => void;
@@ -233,10 +231,10 @@ export class GPTPopup extends ObservableReactComponent<object> {
      */
     generateImage = (imgDesc: string, imgTarget: Doc, addToCollection?: (doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) => {
         this._imgTargetDoc = imgTarget;
+        SnappingManager.SetChatVisible(true);
         this.addDoc = addToCollection;
         this.setImgUrls([]);
         this.setMode(GPTPopupMode.IMAGE);
-        this.setVisible(true);
         this.setGptProcessing(true);
         this._imageDescription = imgDesc;
 
@@ -259,8 +257,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
      * @param text the text to summarizz
      */
     generateSummary = (text: string) => {
+        SnappingManager.SetChatVisible(true);
         this._textToSummarize = text;
-        this.setVisible(true);
         this.setMode(GPTPopupMode.SUMMARY);
         this.setGptProcessing(true);
         return gptAPICall(text, GPTCallType.SUMMARY)
@@ -274,7 +272,6 @@ export class GPTPopup extends ObservableReactComponent<object> {
      * this.dataJson in the popup.
      */
     generateDataAnalysis = () => {
-        this.setVisible(true);
         this.setGptProcessing(true);
         return gptAPICall(this._dataJson, GPTCallType.DATA, this._dataChatPrompt)
             .then(res => {
@@ -398,7 +395,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
         }
     };
 
-    gptUserInput = (isUserPrompt: boolean) => (
+    gptUserInput = () => (
         <div className="btns-wrapper-gpt">
             <div className="chat-wrapper">
                 <div className="chat-bubbles">
@@ -412,6 +409,15 @@ export class GPTPopup extends ObservableReactComponent<object> {
 
                 <div ref={this._messagesEndRef} style={{ height: '100px' }} />
             </div>
+        </div>
+    );
+
+    promptBox = (isUserPrompt: boolean) => (
+        <>
+            <div className="gptPopup-sortBox">
+                {this.heading(isUserPrompt ? 'ASK' : 'QUIZ')}
+                {this.gptUserInput()}
+            </div>
             <div className="inputWrapper">
                 <input
                     className="searchBox-input"
@@ -423,29 +429,31 @@ export class GPTPopup extends ObservableReactComponent<object> {
                     placeholder={`${isUserPrompt ? 'Have ChatGPT sort, tag, define, or filter your documents for you!' : 'Describe/answer the selected document!'}`}
                 />
             </div>
-        </div>
+        </>
     );
 
-    promptBox = (isUserPrompt: boolean) => (
-        <div className="gptPopup-sortBox" style={{ height: '80%' }}>
-            {this.heading(isUserPrompt ? 'SORTING' : 'QUIZ')}
-            {this._mode === GPTPopupMode.GPT_MENU ? this.gptMenu() : this.gptUserInput(isUserPrompt)}
+    menuBox = () => (
+        <div className="gptPopup-sortBox">
+            {this.heading('CHOOSE')}
+            {this.gptMenu()}
         </div>
     );
 
     imageBox = () => (
-        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+        <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', overflow: 'auto', height: '100%', pointerEvents: 'all' }}>
             {this.heading('GENERATED IMAGE')}
             <div className="image-content-wrapper">
                 {this._imgUrls.map((rawSrc, i) => (
-                    <div key={rawSrc[0] + i} className="img-wrapper">
-                        <div className="img-container">
-                            <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
+                    <>
+                        <div key={rawSrc[0] + i} className="img-wrapper">
+                            <div className="img-container">
+                                <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
+                            </div>
                         </div>
                         <div className="btn-container">
                             <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
                         </div>
-                    </div>
+                    </>
                 ))}
             </div>
             {this._gptProcessing ? null : (
@@ -461,7 +469,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
 
     summaryBox = () => (
         <>
-            <div>
+            <div style={{ height: 'calc(100% - 60px)', overflow: 'auto' }}>
                 {this.heading('SUMMARY')}
                 <div className="gptPopup-content-wrapper">
                     {!this._gptProcessing &&
@@ -481,7 +489,7 @@ export class GPTPopup extends ObservableReactComponent<object> {
                 </div>
             </div>
             {!this._gptProcessing && (
-                <div className="btns-wrapper">
+                <div className="btns-wrapper" style={{ position: 'absolute', bottom: 0, width: 'calc(100% - 32px)' }}>
                     {this._stopAnimatingResponse ? (
                         <>
                             <IconButton tooltip="Generate Again" onClick={() => this.generateSummary(this._textToSummarize + ' ')} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(SettingsManager.userVariantColor)} />
@@ -571,19 +579,18 @@ export class GPTPopup extends ObservableReactComponent<object> {
                 <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />
             ) : (
                 <>
-                    {(this._mode === GPTPopupMode.USER_PROMPT || this._mode === GPTPopupMode.QUIZ_RESPONSE) && (
-                        <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} style={{ right: '50px', position: 'absolute' }} />
-                    )}
                     <Toggle
                         tooltip="Clear Chat filter"
                         toggleType={ToggleType.BUTTON}
                         type={Type.PRIM}
                         toggleStatus={Doc.hasDocFilter(this._collectionContext, 'tags', '#chat')}
                         text={Doc.hasDocFilter(this._collectionContext, 'tags', '#chat') ? 'filtered' : ''}
-                        color="red"
+                        color={Doc.hasDocFilter(this._collectionContext, 'tags', '#chat') ? 'red' : 'transparent'}
                         onClick={() => this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', '#chat', 'remove')}
                     />
-                    <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />
+                    {(this._mode === GPTPopupMode.USER_PROMPT || this._mode === GPTPopupMode.QUIZ_RESPONSE) && (
+                        <IconButton color={StrCast(SettingsManager.userVariantColor)} tooltip="back" icon={<CgCornerUpLeft size="16px" />} onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} />
+                    )}
                 </>
             )}
         </div>
@@ -591,20 +598,19 @@ export class GPTPopup extends ObservableReactComponent<object> {
 
     render() {
         return (
-            <div className="gptPopup-summary-box" style={{ display: this.Visible ? 'flex' : 'none' }}>
+            <div className="gptPopup-summary-box" style={{ display: SnappingManager.ChatVisible ? 'flex' : 'none', overflow: 'auto' }}>
                 {(() => {
                     //prettier-ignore
                     switch (this._mode) {
-                        case GPTPopupMode.GPT_MENU:
-                        case GPTPopupMode.USER_PROMPT:
+                        case GPTPopupMode.USER_PROMPT: 
                         case GPTPopupMode.QUIZ_RESPONSE: return this.promptBox(this._mode === GPTPopupMode.USER_PROMPT);
+                        case GPTPopupMode.GPT_MENU:      return this.menuBox();
                         case GPTPopupMode.SUMMARY:       return this.summaryBox();
                         case GPTPopupMode.DATA:          return this.dataAnalysisBox();
                         case GPTPopupMode.IMAGE:         return this.imageBox();
                         default:                         return null;
                 }
                 })()}
-                <div className="resize-handle" />
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 515707c4561eb526426b8fa07dd50bd499fb91cc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 25 Feb 2025 01:03:25 -0500
Subject: added a hideUI option to hide buttons.  fixed a mess of runtime
 warnings mostly related to how scss files can be included in each other

---
 .../components/src/components/Button/Button.scss   | 184 +++++------
 .../src/components/ColorPicker/ColorPicker.scss    |  36 +--
 .../src/components/Dropdown/Dropdown.scss          | 223 +++++++-------
 .../components/DropdownSearch/DropdownSearch.scss  | 207 +++++++------
 .../src/components/EditableText/EditableText.scss  | 224 +++++++-------
 .../src/components/FormInput/FormInput.scss        | 112 +++----
 .../components/src/components/Group/Group.scss     |  23 +-
 .../src/components/IconButton/IconButton.scss      | 190 ++++++------
 .../components/src/components/ListBox/ListBox.scss |   4 +-
 .../src/components/ListItem/ListItem.scss          | 132 ++++----
 .../components/src/components/Modal/Modal.scss     |  74 ++---
 .../src/components/MultiToggle/MultiToggle.scss    |   6 +-
 .../components/NumberDropdown/NumberDropdown.scss  |   7 +-
 .../src/components/NumberInput/NumberInput.scss    |   4 +-
 .../components/src/components/Popup/Popup.scss     |  40 +--
 .../components/src/components/Slider/Slider.scss   | 305 +++++++++----------
 .../components/src/components/Slider/Slider.tsx    | 338 +++++++++++----------
 .../src/components/Template/Template.scss          |   6 +-
 .../components/src/components/Toggle/Toggle.scss   | 122 ++++----
 .../components/src/global/globalCssVariables.scss  | 110 +++----
 src/client/util/CaptureManager.scss                |   2 -
 src/client/util/CurrentUserUtils.ts                |  14 +-
 src/client/util/SettingsManager.scss               |   2 -
 src/client/util/SnappingManager.ts                 |   3 +
 src/client/util/reportManager/ReportManager.scss   |   4 +-
 src/client/views/AntimodeMenu.scss                 |   6 +-
 src/client/views/ContextMenu.scss                  |  16 +-
 src/client/views/DashboardView.scss                |  16 +-
 src/client/views/DocumentButtonBar.scss            |  23 +-
 src/client/views/DocumentDecorations.scss          |  38 +--
 src/client/views/LightboxView.tsx                  |   4 +-
 src/client/views/Main.scss                         |  12 +-
 src/client/views/Main.tsx                          |   3 +-
 src/client/views/MainView.scss                     |  18 +-
 src/client/views/MainView.tsx                      |   8 +-
 src/client/views/PropertiesButtons.scss            |  20 +-
 src/client/views/PropertiesSection.scss            |   2 -
 src/client/views/PropertiesView.scss               |   6 +-
 src/client/views/TemplateMenu.scss                 |   8 +-
 src/client/views/animationtimeline/Region.scss     |  10 +-
 src/client/views/animationtimeline/Timeline.scss   |   6 +-
 .../views/animationtimeline/TimelineMenu.scss      |  16 +-
 .../views/animationtimeline/TimelineOverview.scss  |   2 +-
 src/client/views/animationtimeline/Track.scss      |   6 +-
 .../views/collections/CollectionCardDeckView.scss  |   2 -
 .../collections/CollectionCarousel3DView.scss      |  12 +-
 .../views/collections/CollectionDockingView.scss   |  22 +-
 src/client/views/collections/CollectionMenu.scss   |   6 +-
 .../collections/CollectionNoteTakingView.scss      |  22 +-
 .../collections/CollectionStackedTimeline.scss     |  26 +-
 .../views/collections/CollectionStackingView.scss  |  22 +-
 .../views/collections/CollectionTreeView.scss      |  10 +-
 src/client/views/collections/CollectionView.scss   |   6 +-
 src/client/views/collections/TabDocView.scss       |   2 +-
 src/client/views/collections/TreeView.scss         |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.scss |  16 +-
 .../collectionFreeForm/ImageLabelHandler.tsx       |  14 +-
 .../collectionLinear/CollectionLinearView.scss     |  22 +-
 .../collectionSchema/CollectionSchemaView.scss     |  40 ++-
 src/client/views/linking/LinkMenu.scss             |   2 +-
 src/client/views/linking/LinkMenuItem.scss         |   8 +-
 .../views/newlightbox/ButtonMenu/ButtonMenu.scss   |  12 +-
 .../views/newlightbox/ExploreView/ExploreView.scss |  48 +--
 .../views/newlightbox/Header/LightboxHeader.scss   |  28 +-
 .../RecommendationList/RecommendationList.scss     | 179 ++++++-----
 .../components/EditableText/EditableText.scss      |  46 +--
 .../components/Recommendation/Recommendation.scss  | 122 ++++----
 .../components/SkeletonDoc/SkeletonDoc.scss        | 133 ++++----
 .../newlightbox/components/Template/Template.scss  |  12 +-
 src/client/views/nodes/AudioBox.scss               |  42 +--
 .../views/nodes/DataVizBox/components/Chart.scss   |  10 +-
 src/client/views/nodes/DocumentLinksButton.scss    |  18 +-
 src/client/views/nodes/DocumentView.scss           |   6 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/EquationBox.scss            |   2 -
 .../views/nodes/FontIconBox/FontIconBox.scss       |  36 +--
 src/client/views/nodes/IconTagBox.scss             |   4 +-
 src/client/views/nodes/KeyValueBox.scss            |  22 +-
 src/client/views/nodes/KeyValuePair.scss           |   2 +-
 src/client/views/nodes/LinkDescriptionPopup.scss   |  10 +-
 src/client/views/nodes/MapBox/AnimationUtility.ts  |   4 +-
 src/client/views/nodes/MapBox/MapBox.scss          |  16 +-
 src/client/views/nodes/PDFBox.scss                 |   8 +-
 src/client/views/nodes/VideoBox.scss               |  16 +-
 src/client/views/nodes/WebBox.scss                 |   4 +-
 .../nodes/chatbot/chatboxcomponents/ChatBox.scss   |  11 +-
 .../views/nodes/formattedText/DashFieldView.scss   |   4 +-
 .../nodes/formattedText/FormattedTextBox.scss      |  20 +-
 .../views/nodes/formattedText/RichTextMenu.scss    |   4 +-
 src/client/views/nodes/imageEditor/ImageEditor.tsx |   8 +-
 src/client/views/nodes/trails/PresBox.scss         | 142 ++++-----
 src/client/views/search/SearchBox.scss             |  10 +-
 src/client/views/topbar/TopBar.scss                |  29 +-
 93 files changed, 1909 insertions(+), 1931 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/packages/components/src/components/Button/Button.scss b/packages/components/src/components/Button/Button.scss
index a31923e6d..bbe2e2470 100644
--- a/packages/components/src/components/Button/Button.scss
+++ b/packages/components/src/components/Button/Button.scss
@@ -1,118 +1,118 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .button-container {
-  position: relative;
-  width: fit-content;
-  padding: $padding;
-  cursor: pointer;
-  overflow: hidden;
-  user-select: none;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  gap: 5px;
-  font-family: $default-font;
-  border-radius: $standard-border-radius;
-  white-space: nowrap;
-  transition: 0.4s;
-  border: solid 1px;
-  border-color: transparent;
-  pointer-events: all;
-
-  &.icon {
-    padding: 0;
-    gap: 0;
-  }
-
-  .button-content {
+    position: relative;
+    width: fit-content;
+    padding: global.$padding;
+    cursor: pointer;
+    overflow: hidden;
+    user-select: none;
     display: flex;
     justify-content: center;
     align-items: center;
-    width: fit-content;
-    height: 100%;
-    z-index: 1;
     gap: 5px;
+    font-family: global.$default-font;
+    border-radius: global.$standard-border-radius;
+    white-space: nowrap;
+    transition: 0.4s;
+    border: solid 1px;
+    border-color: transparent;
+    pointer-events: all;
 
-    .icon {
-      display: flex;
-      justify-content: center;
-      align-items: center;
+    &.icon {
+        padding: 0;
+        gap: 0;
     }
-  }
-
-  .background {
-    width: 100%;
-    height: 100%;
-    z-index: 0;
-    left: 0;
-    top: 0;
-    position: absolute;
-    transition: 0.4s;
-  }
 
-  &.inactive {
-    &:hover {
-      .background {
-        filter: opacity(0) !important;
-      }
+    .button-content {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: fit-content;
+        height: 100%;
+        z-index: 1;
+        gap: 5px;
+
+        .icon {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
     }
-  }
 
-  &.primary {
     .background {
-      filter: opacity(0);
-
-      &.active {
-        filter: opacity(0.2) !important;
-      }
+        width: 100%;
+        height: 100%;
+        z-index: 0;
+        left: 0;
+        top: 0;
+        position: absolute;
+        transition: 0.4s;
     }
 
-    &:hover{
-      .background {
-        filter: opacity(0.2)
-      }
+    &.inactive {
+        &:hover {
+            .background {
+                filter: opacity(0) !important;
+            }
+        }
     }
-  }
 
-  &.secondary {
-    .background {
-      filter: opacity(0);
+    &.primary {
+        .background {
+            filter: opacity(0);
 
-      &.active {
-        filter: opacity(0.2) !important;
-      }
-    }
+            &.active {
+                filter: opacity(0.2) !important;
+            }
+        }
 
-    &:hover{
-      .background {
-        filter: opacity(0.2)
-      }
+        &:hover {
+            .background {
+                filter: opacity(0.2);
+            }
+        }
     }
-  }
 
-  &.tertiary {
-    &:hover{
-      box-shadow: $standard-shadow;
-    }
+    &.secondary {
+        .background {
+            filter: opacity(0);
 
-    .background {
-      filter: opacity(1) !important;
+            &.active {
+                filter: opacity(0.2) !important;
+            }
+        }
+
+        &:hover {
+            .background {
+                filter: opacity(0.2);
+            }
+        }
     }
 
-    &:hover{
-      .background {
-        filter: brightness(0.8);
-      }
+    &.tertiary {
+        &:hover {
+            box-shadow: global.$standard-shadow;
+        }
+
+        .background {
+            filter: opacity(1) !important;
+        }
+
+        &:hover {
+            .background {
+                filter: brightness(0.8);
+            }
+        }
     }
-  }
 
-  .label {
-    position: absolute;
-    bottom: 0;
-    width: 100%;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    font-size: $xsmall-fontSize;
-  }
+    .label {
+        position: absolute;
+        bottom: 0;
+        width: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: global.$xsmall-fontSize;
+    }
 }
diff --git a/packages/components/src/components/ColorPicker/ColorPicker.scss b/packages/components/src/components/ColorPicker/ColorPicker.scss
index e3ed32a45..32b912fe5 100644
--- a/packages/components/src/components/ColorPicker/ColorPicker.scss
+++ b/packages/components/src/components/ColorPicker/ColorPicker.scss
@@ -1,23 +1,23 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .colorPicker-container {
-  display: flex;
-  border-radius: $standard-border-radius;
-  width: fit-content;
-  height: fit-content;
-  position: relative;
-
-  .colorPicker-toggle {
-    width: 100%;
-    height: 100%;
-    position: relative;
-    cursor: pointer;
-  }
-
-  .colorPicker-popup {
-    position: absolute;
-    top: calc(100% + 5px);
+    display: flex;
+    border-radius: global.$standard-border-radius;
     width: fit-content;
     height: fit-content;
-  }
+    position: relative;
+
+    .colorPicker-toggle {
+        width: 100%;
+        height: 100%;
+        position: relative;
+        cursor: pointer;
+    }
+
+    .colorPicker-popup {
+        position: absolute;
+        top: calc(100% + 5px);
+        width: fit-content;
+        height: fit-content;
+    }
 }
diff --git a/packages/components/src/components/Dropdown/Dropdown.scss b/packages/components/src/components/Dropdown/Dropdown.scss
index 34ed84004..f9ea2711a 100644
--- a/packages/components/src/components/Dropdown/Dropdown.scss
+++ b/packages/components/src/components/Dropdown/Dropdown.scss
@@ -1,135 +1,136 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .dropdown {
-  margin-top: 10px;
+    margin-top: 10px;
 }
 
 .divider {
-  height: 1px;
-  width: 100%;
-  background: $medium-gray;
+    height: 1px;
+    width: 100%;
+    background: global.$medium-gray;
 }
 
 .dropdown-container {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  min-width: fit-content;
-  width: 100%;
-  border-radius: $standard-border-radius;
-  height: 100%;
-  position: relative;
-  transition: 0.4s;
-
-  .dropdown-list {
-    position: absolute;
-    top: 100%;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    min-width: fit-content;
     width: 100%;
-  }
-  .dropdown-toggle-mini,
-  .dropdown-toggle {
-    width: calc(100% - 2px);
-    display: grid;
-    grid-template-columns: calc(100% - 30px) 30px;
-    grid-template-areas: 'button end';
-    grid-template-rows: 1fr;
+    border-radius: global.$standard-border-radius;
+    height: 100%;
     position: relative;
-    align-items: center;
-    border: solid 1px;
-    border-color: transparent;
-    border-radius: $standard-border-radius;
-    overflow: hidden;
-
-    &.inactive {
-      filter: opacity(0.5);
-      pointer-events: none;
-      cursor: not-allowed;
-    }
+    transition: 0.4s;
 
-    .background {
-      width: 100%;
-      height: 100%;
-      z-index: 0;
-      position: absolute;
-      transition: 0.4s;
+    .dropdown-list {
+        position: absolute;
+        top: 100%;
+        width: 100%;
     }
+    .dropdown-toggle-mini,
+    .dropdown-toggle {
+        width: calc(100% - 2px);
+        display: grid;
+        grid-template-columns: calc(100% - 30px) 30px;
+        grid-template-areas: 'button end';
+        grid-template-rows: 1fr;
+        position: relative;
+        align-items: center;
+        border: solid 1px;
+        border-color: transparent;
+        border-radius: global.$standard-border-radius;
+        overflow: hidden;
 
-    &.inactive {
-      &:hover {
-        .background {
-          filter: opacity(0) !important;
+        &.inactive {
+            filter: opacity(0.5);
+            pointer-events: none;
+            cursor: not-allowed;
         }
-      }
-    }
-  
-    &.primary {
-      .background {
-        filter: opacity(0);
-  
-        &.active {
-          filter: opacity(0.2) !important;
-        }
-      }
-  
-      &:hover{
+
         .background {
-          filter: opacity(0.2)
+            width: 100%;
+            height: 100%;
+            z-index: 0;
+            position: absolute;
+            transition: 0.4s;
         }
-      }
-    }
-  
-    &.secondary {
-      .background {
-        filter: opacity(0);
-  
-        &.active {
-          filter: opacity(0.2) !important;
+
+        &.inactive {
+            &:hover {
+                .background {
+                    filter: opacity(0) !important;
+                }
+            }
         }
-      }
-  
-      &:hover{
-        .background {
-          filter: opacity(0.2)
+
+        &.primary {
+            .background {
+                filter: opacity(0);
+
+                &.active {
+                    filter: opacity(0.2) !important;
+                }
+            }
+
+            &:hover {
+                .background {
+                    filter: opacity(0.2);
+                }
+            }
         }
-      }
-    }
-  
-    &.tertiary {  
-      &:hover{
-        box-shadow: $standard-button-shadow;
-      }
-  
-      &:hover{
-        .background {
-          filter: brightness(0.8);
+
+        &.secondary {
+            .background {
+                filter: opacity(0);
+
+                &.active {
+                    filter: opacity(0.2) !important;
+                }
+            }
+
+            &:hover {
+                .background {
+                    filter: opacity(0.2);
+                }
+            }
         }
-      }
-    }
 
-    .toggle-button {
-      grid-area: button;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      width: 100%;
-      height: 100%;
-      min-width: 70px;
-      justify-self: center;
-    }
+        &.tertiary {
+            &:hover {
+                box-shadow: global.$standard-button-shadow;
+            }
 
-    .toggle-caret {
-      cursor: pointer;
-      grid-area: end;
-      display: flex;
-      justify-content: flex-end;
-      align-items: center;
-      justify-self: center;
+            &:hover {
+                .background {
+                    filter: brightness(0.8);
+                }
+            }
+        }
+
+        .toggle-button {
+            grid-area: button;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            width: 100%;
+            height: 100%;
+            min-width: 70px;
+            justify-self: center;
+        }
+
+        .toggle-caret {
+            cursor: pointer;
+            grid-area: end;
+            display: flex;
+            justify-content: flex-end;
+            align-items: center;
+            justify-self: center;
+        }
     }
-  }
-  .dropdown-toggle-mini {
-    .toggle-caret {
-      position: absolute;
-      top:0; left:0;
+    .dropdown-toggle-mini {
+        .toggle-caret {
+            position: absolute;
+            top: 0;
+            left: 0;
+        }
     }
-  }
 }
diff --git a/packages/components/src/components/DropdownSearch/DropdownSearch.scss b/packages/components/src/components/DropdownSearch/DropdownSearch.scss
index e111c822b..d937df540 100644
--- a/packages/components/src/components/DropdownSearch/DropdownSearch.scss
+++ b/packages/components/src/components/DropdownSearch/DropdownSearch.scss
@@ -1,123 +1,122 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .dropdownsearch {
-  margin-top: 10px;
+    margin-top: 10px;
 }
 
 .divider {
-  height: 1px;
-  width: 100%;
-  background: $medium-gray;
+    height: 1px;
+    width: 100%;
+    background: global.$medium-gray;
 }
 
 .dropdownsearch-container {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  min-width: fit-content;
-  border-radius: $standard-border-radius;
-  height: 100%;
-  position: relative;
-  transition: 0.4s;
-
-  .dropdownsearch-list {
-    position: absolute;
-    top: 100%;
-    width: 100%;
-  }
-
-  .dropdownsearch-toggle {
-    width: 100%;
-    display: grid;
-    grid-template-columns: calc(100% - 30px) 30px;
-    grid-template-areas: 'button end';
-    grid-template-rows: 1fr;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    min-width: fit-content;
+    border-radius: global.$standard-border-radius;
+    height: 100%;
     position: relative;
-    align-items: center;
-    border: solid 1px;
-    border-color: transparent;
-    border-radius: $standard-border-radius;
-    overflow: hidden;
-
-    .toggle-background {
-      width: 100%;
-      height: 100%;
-      z-index: 0;
-      position: absolute;
-      transition: 0.4s;
-
-      &.active {
-        filter: opacity(0.2) !important;
-      }
+    transition: 0.4s;
+
+    .dropdownsearch-list {
+        position: absolute;
+        top: 100%;
+        width: 100%;
     }
 
-    &.primary {
-      color: $medium-blue;
-      .toggle-background {
-        background: $medium-blue;
-        filter: opacity(0);
-      }
-  
-      &:hover{
+    .dropdownsearch-toggle {
+        width: 100%;
+        display: grid;
+        grid-template-columns: calc(100% - 30px) 30px;
+        grid-template-areas: 'button end';
+        grid-template-rows: 1fr;
+        position: relative;
+        align-items: center;
+        border: solid 1px;
+        border-color: transparent;
+        border-radius: global.$standard-border-radius;
+        overflow: hidden;
+
         .toggle-background {
-          filter: opacity(0.2)
+            width: 100%;
+            height: 100%;
+            z-index: 0;
+            position: absolute;
+            transition: 0.4s;
+
+            &.active {
+                filter: opacity(0.2) !important;
+            }
         }
-      }
-  
-    }
-  
-    &.secondary {
-      .toggle-background {
-        background: $medium-blue;
-        filter: opacity(0);
-      }
-  
-      border: solid 1px $medium-blue;
-      color: $medium-blue;
-  
-      &:hover{
-        .toggle-background {
-          filter: opacity(0.2)
+
+        &.primary {
+            color: global.$medium-blue;
+            .toggle-background {
+                background: global.$medium-blue;
+                filter: opacity(0);
+            }
+
+            &:hover {
+                .toggle-background {
+                    filter: opacity(0.2);
+                }
+            }
         }
-      }
-    }
-  
-    &.tertiary {
-      color: white;
-  
-      .toggle-background {
-        background: $medium-blue;
-      }
-  
-      &:hover{
-        box-shadow: $standard-button-shadow;
-      }
-  
-      &:hover{
-        .toggle-background {
-          filter: brightness(0.8);
+
+        &.secondary {
+            .toggle-background {
+                background: global.$medium-blue;
+                filter: opacity(0);
+            }
+
+            border: solid 1px global.$medium-blue;
+            color: global.$medium-blue;
+
+            &:hover {
+                .toggle-background {
+                    filter: opacity(0.2);
+                }
+            }
         }
-      }
-    }
 
-    .toggle-button {
-      grid-area: button;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      width: 100%;
-      height: 100%;
-      min-width: 70px;
-      justify-self: center;
-    }
+        &.tertiary {
+            color: white;
+
+            .toggle-background {
+                background: global.$medium-blue;
+            }
 
-    .toggle-caret {
-      cursor: pointer;
-      grid-area: end;
-      display: flex;
-      justify-content: flex-end;
-      align-items: center;
-      justify-self: center;
+            &:hover {
+                box-shadow: global.$standard-button-shadow;
+            }
+
+            &:hover {
+                .toggle-background {
+                    filter: brightness(0.8);
+                }
+            }
+        }
+
+        .toggle-button {
+            grid-area: button;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            width: 100%;
+            height: 100%;
+            min-width: 70px;
+            justify-self: center;
+        }
+
+        .toggle-caret {
+            cursor: pointer;
+            grid-area: end;
+            display: flex;
+            justify-content: flex-end;
+            align-items: center;
+            justify-self: center;
+        }
     }
-  }
 }
diff --git a/packages/components/src/components/EditableText/EditableText.scss b/packages/components/src/components/EditableText/EditableText.scss
index 19e5af2cd..15965e97e 100644
--- a/packages/components/src/components/EditableText/EditableText.scss
+++ b/packages/components/src/components/EditableText/EditableText.scss
@@ -1,131 +1,129 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .editableText-container {
-  position: relative;
-  width: fit-content;
-  border: solid 1px;
-  border-color: transparent;
-  border-radius: $standard-border-radius;
-  font-family: $default-font;
-  overflow: hidden;
-  padding: $padding;
-
-  .password {
-    position: absolute;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    height: 100%;
-    right: 0;
-    top: 0;
-  }
-
-  .editableText-background {
-    width: 100%;
-    height: 100%;
-    z-index: -1;
-    position: absolute;
-    transition: 0.4s;
-    top: 0;
-    left: 0;
-  }
-
-  &.primary {
-
-    &:focus-within {
-      .editableText-background {
-        filter: opacity(0.2) !important;
-      }
+    position: relative;
+    width: fit-content;
+    border: solid 1px;
+    border-color: transparent;
+    border-radius: global.$standard-border-radius;
+    font-family: global.$default-font;
+    overflow: hidden;
+    padding: global.$padding;
+
+    .password {
+        position: absolute;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        height: 100%;
+        right: 0;
+        top: 0;
     }
 
     .editableText-background {
-      filter: opacity(0);
-
-      &.active {
-        filter: opacity(0.2) !important;
-      }
+        width: 100%;
+        height: 100%;
+        z-index: -1;
+        position: absolute;
+        transition: 0.4s;
+        top: 0;
+        left: 0;
     }
 
-    &:hover{
-      .editableText-background {
-        filter: opacity(0.2)
-      }
+    &.primary {
+        &:focus-within {
+            .editableText-background {
+                filter: opacity(0.2) !important;
+            }
+        }
+
+        .editableText-background {
+            filter: opacity(0);
+
+            &.active {
+                filter: opacity(0.2) !important;
+            }
+        }
+
+        &:hover {
+            .editableText-background {
+                filter: opacity(0.2);
+            }
+        }
     }
-  }
-
-  &.secondary {
-    &:focus-within {
-      .editableText-background {
-        filter: opacity(0.2) !important;
-      }
-    }
-
-    .editableText-background {
-      filter: opacity(0);
 
-      &.active {
-        filter: opacity(0.2) !important;
-      }
+    &.secondary {
+        &:focus-within {
+            .editableText-background {
+                filter: opacity(0.2) !important;
+            }
+        }
+
+        .editableText-background {
+            filter: opacity(0);
+
+            &.active {
+                filter: opacity(0.2) !important;
+            }
+        }
+
+        &:hover {
+            .editableText-background {
+                filter: opacity(0.2);
+            }
+        }
     }
 
-    &:hover{
-      .editableText-background {
-        filter: opacity(0.2)
-      }
-    }
-  }
+    &.tertiary {
+        &:hover {
+            box-shadow: global.$standard-shadow;
+        }
 
-  &.tertiary {
-    &:hover{
-      box-shadow: $standard-shadow;
+        &:hover {
+            .editableText-background {
+                filter: brightness(0.8);
+            }
+        }
     }
 
-    &:hover{
-      .editableText-background {
-        filter: brightness(0.8);
-      }
+    .editableText {
+        -webkit-appearance: none;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        font-size: inherit;
+        border: none;
+        outline: none;
+        margin: 0px !important;
+        padding: 0px !important;
+        box-shadow: none !important;
+        background: transparent;
+        color: inherit;
+        z-index: 1;
+
+        &.center {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
     }
-  }
 
-  .editableText {
-    -webkit-appearance: none;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    font-size: inherit;
-    border: none;
-    outline: none;
-    margin: 0px !important;
-    padding: 0px !important;
-    box-shadow: none !important;
-    background: transparent;
-    color: inherit;
-    z-index: 1;
-
-    &.center {
-      display: flex;
-      justify-content: center;
-      align-items: center;
+    .displayText {
+        cursor: text !important;
+        width: 100%;
+        height: 100%;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        display: flex;
+        align-items: center;
+        font-size: inherit;
+        color: inherit;
+        z-index: 1;
+
+        &.center {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
     }
-  }
-  
-  .displayText {
-    cursor: text !important;
-    width: 100%;
-    height: 100%;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    display: flex;
-    align-items: center;
-    font-size: inherit;
-    color: inherit;
-    z-index: 1;
-
-    &.center {
-      display: flex;
-      justify-content: center;
-      align-items: center;
-    }
-  }
 }
-
diff --git a/packages/components/src/components/FormInput/FormInput.scss b/packages/components/src/components/FormInput/FormInput.scss
index db04ff8cf..2554cbd01 100644
--- a/packages/components/src/components/FormInput/FormInput.scss
+++ b/packages/components/src/components/FormInput/FormInput.scss
@@ -1,69 +1,69 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .formInput-container {
-  display: flex;
-  flex-direction: column;
-  width: 100%;
-  height: fit-content;
-  position: relative;
-  margin-top: 20px;
-
-  .formInput {
-    font-family: inherit;
+    display: flex;
+    flex-direction: column;
     width: 100%;
-    border: 0;
-    border-bottom: 2px solid black;
-    outline: 0;
-    font-size: 1rem;
-    color: black;
-    padding: 7px 0;
-    background: transparent;
-    transition: border-color 0.2s;
-
-    &::placeholder {
-      color: transparent;
-    }
+    height: fit-content;
+    position: relative;
+    margin-top: 20px;
 
-    &:focus {
-      ~ .formInput-label {
-        position: absolute;
-        transform: translate(0px, -13px);
-        display: block;
-        transition: 0.2s;
+    .formInput {
+        font-family: inherit;
+        width: 100%;
+        border: 0;
+        border-bottom: 2px solid black;
+        outline: 0;
         font-size: 1rem;
-        font-weight: 700;
-      }
-      padding-bottom: 6px;
-      font-weight: 700;
-      border-width: 3px;
-      border-image: linear-gradient(to right, black, white);
-      border-image-slice: 1;
+        color: black;
+        padding: 7px 0;
+        background: transparent;
+        transition: border-color 0.2s;
+
+        &::placeholder {
+            color: transparent;
+        }
+
+        &:focus {
+            ~ .formInput-label {
+                position: absolute;
+                transform: translate(0px, -13px);
+                display: block;
+                transition: 0.2s;
+                font-size: 1rem;
+                font-weight: 700;
+            }
+            padding-bottom: 6px;
+            font-weight: 700;
+            border-width: 3px;
+            border-image: linear-gradient(to right, black, white);
+            border-image-slice: 1;
+        }
+
+        &:valid {
+            ~ .formInput-label {
+                position: absolute;
+                transform: translate(0px, -13px);
+                display: block;
+                transition: 0.2s;
+                font-size: 1rem;
+            }
+        }
+
+        &:required,
+        &:invalid {
+            box-shadow: none;
+        }
     }
 
-    &:valid {
-      ~ .formInput-label {
+    .formInput-label {
         position: absolute;
-        transform: translate(0px, -13px);
+        top: 0;
+        transform: translate(0px, 8px);
         display: block;
         transition: 0.2s;
         font-size: 1rem;
-      }
+        color: gray;
+        pointer-events: none;
     }
-
-    &:required,
-    &:invalid {
-      box-shadow: none;
-    }
-  }
-
-  .formInput-label {
-    position: absolute;
-    top: 0;
-    transform: translate(0px, 8px);
-    display: block;
-    transition: 0.2s;
-    font-size: 1rem;
-    color: gray;
-    pointer-events: none;
-  }
 }
diff --git a/packages/components/src/components/Group/Group.scss b/packages/components/src/components/Group/Group.scss
index 885472a5d..7cd3dfd9e 100644
--- a/packages/components/src/components/Group/Group.scss
+++ b/packages/components/src/components/Group/Group.scss
@@ -1,16 +1,15 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .group-wrapper {
-  overflow: hidden;
+    overflow: hidden;
 
-  .group-container {
-    width: fit-content;
-    display: flex;
-    flex-flow: row wrap;
-    height: fit-content;
-    flex-flow: row;
-    justify-content: flex-start;
-    align-items: center;
-  }
+    .group-container {
+        width: fit-content;
+        display: flex;
+        flex-flow: row wrap;
+        height: fit-content;
+        flex-flow: row;
+        justify-content: flex-start;
+        align-items: center;
+    }
 }
-
diff --git a/packages/components/src/components/IconButton/IconButton.scss b/packages/components/src/components/IconButton/IconButton.scss
index 9a0b53c0f..f899dc50f 100644
--- a/packages/components/src/components/IconButton/IconButton.scss
+++ b/packages/components/src/components/IconButton/IconButton.scss
@@ -1,121 +1,119 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .iconButton-container {
-  position: relative;
-  cursor: pointer;
-  overflow: hidden;
-  user-select: none;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  font-family: $default-font;
-  border-radius: $standard-border-radius;
-  white-space: nowrap;
-  transition: 0.4s;
-  border: solid 1px;
-  border-color: transparent;
-  pointer-events: all;
-
-
-  .iconButton-content {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    align-items: center;
-    height: 100%;
-    width: 100%;
-    z-index: 1;
-    font-family: Verdana, sans-serif;
-    font-weight: 500;
-  }
-
-  .icon {
-    z-index: 1;
+    position: relative;
+    cursor: pointer;
+    overflow: hidden;
+    user-select: none;
     display: flex;
     justify-content: center;
     align-items: center;
-  }
-
-  .background {
-    width: 100%;
-    height: 100%;
-    z-index: 0;
-    position: absolute;
+    font-family: global.$default-font;
+    border-radius: global.$standard-border-radius;
+    white-space: nowrap;
     transition: 0.4s;
-  }
+    border: solid 1px;
+    border-color: transparent;
+    pointer-events: all;
+
+    .iconButton-content {
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        height: 100%;
+        width: 100%;
+        z-index: 1;
+        font-family: Verdana, sans-serif;
+        font-weight: 500;
+    }
 
-  &.inactive {
-    .background {
-      filter: opacity(0) !important;
+    .icon {
+        z-index: 1;
+        display: flex;
+        justify-content: center;
+        align-items: center;
     }
-  }
 
-  &.primary {
     .background {
-      filter: opacity(0);
-
-      &.active {
-        filter: opacity(0.2) !important;
-      }
+        width: 100%;
+        height: 100%;
+        z-index: 0;
+        position: absolute;
+        transition: 0.4s;
     }
 
-    &:hover{
-      .background {
-        filter: opacity(0.2)
-      }
+    &.inactive {
+        .background {
+            filter: opacity(0) !important;
+        }
     }
 
-  }
+    &.primary {
+        .background {
+            filter: opacity(0);
 
-  &.secondary {
-    .background {
-      filter: opacity(0);
+            &.active {
+                filter: opacity(0.2) !important;
+            }
+        }
 
-      &.active {
-        filter: opacity(0.2) !important;
-      }
+        &:hover {
+            .background {
+                filter: opacity(0.2);
+            }
+        }
     }
 
-    &:hover{
-      .background {
-        filter: opacity(0.2)
-      }
-    }
-  }
+    &.secondary {
+        .background {
+            filter: opacity(0);
+
+            &.active {
+                filter: opacity(0.2) !important;
+            }
+        }
 
-  &.tertiary {
-    &:hover{
-      box-shadow: $standard-button-shadow;
+        &:hover {
+            .background {
+                filter: opacity(0.2);
+            }
+        }
     }
 
-    &:hover{
-      .background {
-        filter: brightness(0.8);
-      }
+    &.tertiary {
+        &:hover {
+            box-shadow: global.$standard-button-shadow;
+        }
+
+        &:hover {
+            .background {
+                filter: brightness(0.8);
+            }
+        }
     }
-  }
 
-  .color {
-    position: relative;
-    width: 70%;
-    height: 15%;
-    z-index: 3;
-    margin-top: 2px;
-    border-radius: 10px;
-    outline: solid 0.3px;
-    outline-offset: -0.3px;
-  }
+    .color {
+        position: relative;
+        width: 70%;
+        height: 15%;
+        z-index: 3;
+        margin-top: 2px;
+        border-radius: 10px;
+        outline: solid 0.3px;
+        outline-offset: -0.3px;
+    }
 
-  .iconButton-label {
-    position: relative;
-    z-index: 2;
-    max-width: 100%;
-    overflow: hidden;
-    white-space: normal;
-    display: flex;
-    text-align: center;
-    justify-content: center;
-    align-items: center;
-    font-size: $xsmall-fontSize;
-  }
+    .iconButton-label {
+        position: relative;
+        z-index: 2;
+        max-width: 100%;
+        overflow: hidden;
+        white-space: normal;
+        display: flex;
+        text-align: center;
+        justify-content: center;
+        align-items: center;
+        font-size: global.$xsmall-fontSize;
+    }
 }
diff --git a/packages/components/src/components/ListBox/ListBox.scss b/packages/components/src/components/ListBox/ListBox.scss
index dc449c943..dc2a44513 100644
--- a/packages/components/src/components/ListBox/ListBox.scss
+++ b/packages/components/src/components/ListBox/ListBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .listBox-container {
     position: relative;
@@ -13,4 +13,4 @@
     max-width: 300px;
     padding: 5px;
     gap: 2px;
-}
\ No newline at end of file
+}
diff --git a/packages/components/src/components/ListItem/ListItem.scss b/packages/components/src/components/ListItem/ListItem.scss
index 736078360..8d46605d0 100644
--- a/packages/components/src/components/ListItem/ListItem.scss
+++ b/packages/components/src/components/ListItem/ListItem.scss
@@ -1,78 +1,78 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .listItem-container {
-  position: relative;
-  width: 100%;
-  border-radius: $standard-border-radius;
-  display: flex;
-  justify-content: center;
-  align-items: flex-start;
-  flex-direction: column;
-  cursor: pointer;
-  font-family: Verdana, sans-serif;
-  overflow: hidden;
-  text-align: left;
-
-  .listItem-background {
-    position: absolute;
+    position: relative;
     width: 100%;
-    height: 100%;
-    background: $medium-blue;
-    filter: opacity(0);
-    transition: 0.4s;
-  }
-
-  .listItem-top {
+    border-radius: global.$standard-border-radius;
     display: flex;
-    height: 30px;
-    width: 100%;
-    justify-content: space-between;
-    align-items: center;
-    gap: 20px;
+    justify-content: center;
+    align-items: flex-start;
+    flex-direction: column;
+    cursor: pointer;
+    font-family: Verdana, sans-serif;
+    overflow: hidden;
+    text-align: left;
 
-    .content {
-      display: flex;
-      justify-content: flex-start;
-      align-items: center;
-      padding: $padding;
-      width: 100%;
-      height: 100%;
-      z-index: 1;
-      gap: 5px;
-      font-weight: 500;
-
-      .text {
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
+    .listItem-background {
+        position: absolute;
         width: 100%;
-      }
+        height: 100%;
+        background: global.$medium-blue;
+        filter: opacity(0);
+        transition: 0.4s;
     }
 
-    .shortcut {
-      grid-area: end;
-      padding: $padding;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      font-size: $xsmall-fontSize;
-      font-family: $default-font;
-    }
+    .listItem-top {
+        display: flex;
+        height: 30px;
+        width: 100%;
+        justify-content: space-between;
+        align-items: center;
+        gap: 20px;
 
-    .caret {
-      grid-area: end;
-      display: flex;
-      justify-content: flex-end;
-      align-items: center;
-      justify-self: center;
+        .content {
+            display: flex;
+            justify-content: flex-start;
+            align-items: center;
+            padding: global.$padding;
+            width: 100%;
+            height: 100%;
+            z-index: 1;
+            gap: 5px;
+            font-weight: 500;
+
+            .text {
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                width: 100%;
+            }
+        }
+
+        .shortcut {
+            grid-area: end;
+            padding: global.$padding;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-size: global.$xsmall-fontSize;
+            font-family: global.$default-font;
+        }
+
+        .caret {
+            grid-area: end;
+            display: flex;
+            justify-content: flex-end;
+            align-items: center;
+            justify-self: center;
+        }
     }
-  }
 
-  .listItem-description {
-    font-size: $small-fontSize;
-    display: flex;
-    padding: 0px 5px 10px 5px;
-    justify-content: flex-start;
-    width: calc(100% - 10px);
-  }
+    .listItem-description {
+        font-size: global.$small-fontSize;
+        display: flex;
+        padding: 0px 5px 10px 5px;
+        justify-content: flex-start;
+        width: calc(100% - 10px);
+    }
 }
diff --git a/packages/components/src/components/Modal/Modal.scss b/packages/components/src/components/Modal/Modal.scss
index c0667ed26..a5698432d 100644
--- a/packages/components/src/components/Modal/Modal.scss
+++ b/packages/components/src/components/Modal/Modal.scss
@@ -1,46 +1,46 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .modal-container {
-  top: 0px;
-  left: 0px;
-  width: 100vw;
-  height: 100vh;
-  position: fixed;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  z-index: 100;
-
-  .modal-popup {
-    position: relative;
+    top: 0px;
+    left: 0px;
+    width: 100vw;
+    height: 100vh;
+    position: fixed;
     display: flex;
-    flex-direction: column;
-    align-items: left;
-    z-index: 10;
-    width: 400px;
-    height: fit-content;
-    padding: 20px;
-    border-radius: $standard-border-radius;
-    font-weight: bold;
-    font-size: 1.5rem;
+    justify-content: center;
+    align-items: center;
+    z-index: 100;
+
+    .modal-popup {
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        align-items: left;
+        z-index: 10;
+        width: 400px;
+        height: fit-content;
+        padding: 20px;
+        border-radius: global.$standard-border-radius;
+        font-weight: bold;
+        font-size: 1.5rem;
 
-    .modal-closeButton {
-      top: -15px;
-      right: -15px;
-      position: absolute;
-      width: fit-content;
-      height: fit-content;
+        .modal-closeButton {
+            top: -15px;
+            right: -15px;
+            position: absolute;
+            width: fit-content;
+            height: fit-content;
+        }
     }
-  }
 }
 
 .modal-background {
-  z-index: 9;
-  position: absolute;
-  top: -10vh;
-  left: -10vw;
-  backdrop-filter: blur(15px);
-  width: 200vw;
-  height: 200vh;
-  background: $modal-background;
+    z-index: 9;
+    position: absolute;
+    top: -10vh;
+    left: -10vw;
+    backdrop-filter: blur(15px);
+    width: 200vw;
+    height: 200vh;
+    background: global.$modal-background;
 }
diff --git a/packages/components/src/components/MultiToggle/MultiToggle.scss b/packages/components/src/components/MultiToggle/MultiToggle.scss
index 2522549e9..854abdca0 100644
--- a/packages/components/src/components/MultiToggle/MultiToggle.scss
+++ b/packages/components/src/components/MultiToggle/MultiToggle.scss
@@ -1,5 +1 @@
-@import '../../global/globalCssVariables.scss';
-
-.multiToggle-container {
-    
-}
\ No newline at end of file
+@use '../../global/globalCssVariables.scss' as global;
diff --git a/packages/components/src/components/NumberDropdown/NumberDropdown.scss b/packages/components/src/components/NumberDropdown/NumberDropdown.scss
index 4ed5855d9..b3111623b 100644
--- a/packages/components/src/components/NumberDropdown/NumberDropdown.scss
+++ b/packages/components/src/components/NumberDropdown/NumberDropdown.scss
@@ -1,7 +1,4 @@
-@import '../../global/globalCssVariables.scss';
-
-.numberDropdown-container {
-}
+@use '../../global/globalCssVariables.scss' as global;
 
 .form-wrapper {
     .iconButton-label {
@@ -14,6 +11,6 @@
         text-align: center;
         justify-content: center;
         align-items: center;
-        font-size: $xsmall-fontSize !important;
+        font-size: global.$xsmall-fontSize !important;
     }
 }
diff --git a/packages/components/src/components/NumberInput/NumberInput.scss b/packages/components/src/components/NumberInput/NumberInput.scss
index 2a562d395..887d64857 100644
--- a/packages/components/src/components/NumberInput/NumberInput.scss
+++ b/packages/components/src/components/NumberInput/NumberInput.scss
@@ -1,5 +1,5 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .numberInput-container {
     width: 100%;
-}
\ No newline at end of file
+}
diff --git a/packages/components/src/components/Popup/Popup.scss b/packages/components/src/components/Popup/Popup.scss
index 39dd2c947..3087293f1 100644
--- a/packages/components/src/components/Popup/Popup.scss
+++ b/packages/components/src/components/Popup/Popup.scss
@@ -1,30 +1,30 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .popup-wrapper {
-  width: fit-content;
-
-  &.fillWidth {
-    width: 100%;
-  }
-  
-  .trigger-container {
     width: fit-content;
-    height: fit-content;
 
     &.fillWidth {
-      width: 100%;
+        width: 100%;
+    }
+
+    .trigger-container {
+        width: fit-content;
+        height: fit-content;
+
+        &.fillWidth {
+            width: 100%;
+        }
     }
-  }
 }
 
 .popup-container {
-  display: flex;
-  height: fit-content;
-  min-width: fit-content;
-  width: fit-content;
-  position: relative;
-  border: solid 1px $black;
-  border-radius: $standard-border-radius;
-  overflow: hidden;
-  background: $white;
+    display: flex;
+    height: fit-content;
+    min-width: fit-content;
+    width: fit-content;
+    position: relative;
+    border: solid 1px global.$black;
+    border-radius: global.$standard-border-radius;
+    overflow: hidden;
+    background: global.$white;
 }
diff --git a/packages/components/src/components/Slider/Slider.scss b/packages/components/src/components/Slider/Slider.scss
index 9a9fc6172..a653f024f 100644
--- a/packages/components/src/components/Slider/Slider.scss
+++ b/packages/components/src/components/Slider/Slider.scss
@@ -1,168 +1,163 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .slider-container {
-  display: flex;
-  position: relative;
-  justify-content: center;
-  align-items: center;
-  min-width: 200px;
-  width: 100%;
-  height: 100%;
-  font-family: $default-font;
-
-  .selected-range {
-    width: 100%;
-    background: $medium-blue;
-  }
-
-  .range {
-    position: absolute;
-    background: $light-gray;
-  }
-
-  .box-minmax{
-    width: 100%;
     display: flex;
-    justify-content: space-between;
-    font-size: 20px;
-    color: $medium-blue;
-    position: absolute;
-    top: 110%;
-  }
-  .range-slider {
-    margin: 0px;
-    position: absolute;
+    position: relative;
+    justify-content: center;
+    align-items: center;
+    min-width: 200px;
     width: 100%;
     height: 100%;
-    top: 0px;
-    left: 0px;
-
-    .rs-label-container {
-      display: flex;
-      position: absolute;
-      justify-content: center;
-      align-items: center;
-      overflow: visible;
-      border-radius: $standard-border-radius;
-      z-index: 45;
-      pointer-events: none;
-
-      .rs-label {
-        display: flex;
-        font-size: smaller;
-        white-space: nowrap;
-        border-radius: 100%;
-        text-align: center;
-        text-wrap: wrap;
-        word-break: break-all;
-        justify-content: center;
-        align-items: center;
-        font-family: $default-font;
-        user-select: none;
-        pointer-events: none;
-        top: 0px;
-        width: fit-content;
-        border-radius: $standard-border-radius;
-        z-index: 40;
-      }
-
-    }
-  
-    .rs-range {
-      width: 100%;
-      position: relative;
-      background: transparent;
-      pointer-events: none;
-      -webkit-appearance: none;
-      margin: 0px;
-      z-index: 20;
-  
-      &:focus {
-        outline: none;
-      }
-  
-      &::-webkit-slider-runnable-track {
-          width: 100%;
-          background: none;
-          cursor: pointer;
-          box-shadow: none;
-          -webkit-appearance: none;
-          pointer-events: none;
-      }
-      &::-moz-range-track {
-          width: 100%;
-          cursor: pointer;
-          box-shadow: none;
-          -webkit-appearance: none;
-          pointer-events: none;
-      }
-    
-    &::-webkit-slider-thumb {
-          cursor: ew-resize;
-          -webkit-appearance: none;
-          pointer-events: auto;
-    }
-    &::-moz-range-thumb {
-          cursor: pointer;
-          -webkit-appearance: none;
-          pointer-events: auto;
-    }
-
-    &::-moz-focus-outer {
-      border: 0;
-    }
-
-    &.xsmall {
-    &::-webkit-slider-runnable-track {
-        height: $xsmall;
-      }
+    font-family: global.$default-font;
 
-      &::-webkit-slider-thumb {
-        height: $xsmall;
-        width: $xsmall;
-        border-radius: $xsmall;
-      }
+    .selected-range {
+        width: 100%;
+        background: global.$medium-blue;
     }
 
-    &.small {
-      &::-webkit-slider-runnable-track {
-        height: $small;
-      }
-
-      &::-webkit-slider-thumb {
-        height: $small;
-        width: $small;
-        border-radius: $small;
-      }
+    .range {
+        position: absolute;
+        background: global.$light-gray;
     }
 
-    &.medium {
-      &::-webkit-slider-runnable-track {
-        height: $medium;
-      }
-
-      &::-webkit-slider-thumb {
-        height: $medium;
-        width: $medium;
-        border-radius: $medium;
-      }
+    .box-minmax {
+        width: 100%;
+        display: flex;
+        justify-content: space-between;
+        font-size: 20px;
+        color: global.$medium-blue;
+        position: absolute;
+        top: 110%;
     }
-
-    &.large {
-      &::-webkit-slider-runnable-track {
-        height: $large;
-      }
-
-      &::-webkit-slider-thumb {
-        height: $large;
-        width: $large;
-        border-radius: $large;
-      }
+    .range-slider {
+        margin: 0px;
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        top: 0px;
+        left: 0px;
+
+        .rs-label-container {
+            display: flex;
+            position: absolute;
+            justify-content: center;
+            align-items: center;
+            overflow: visible;
+            border-radius: global.$standard-border-radius;
+            z-index: 45;
+            pointer-events: none;
+
+            .rs-label {
+                display: flex;
+                font-size: smaller;
+                white-space: nowrap;
+                border-radius: 100%;
+                text-align: center;
+                text-wrap: wrap;
+                word-break: break-all;
+                justify-content: center;
+                align-items: center;
+                font-family: global.$default-font;
+                user-select: none;
+                pointer-events: none;
+                top: 0px;
+                width: fit-content;
+                border-radius: global.$standard-border-radius;
+                z-index: 40;
+            }
+        }
+
+        .rs-range {
+            width: 100%;
+            position: relative;
+            background: transparent;
+            pointer-events: none;
+            -webkit-appearance: none;
+            margin: 0px;
+            z-index: 20;
+
+            &:focus {
+                outline: none;
+            }
+
+            &::-webkit-slider-runnable-track {
+                width: 100%;
+                background: none;
+                cursor: pointer;
+                box-shadow: none;
+                -webkit-appearance: none;
+                pointer-events: none;
+            }
+            &::-moz-range-track {
+                width: 100%;
+                cursor: pointer;
+                box-shadow: none;
+                -webkit-appearance: none;
+                pointer-events: none;
+            }
+
+            &::-webkit-slider-thumb {
+                cursor: ew-resize;
+                -webkit-appearance: none;
+                pointer-events: auto;
+            }
+            &::-moz-range-thumb {
+                cursor: pointer;
+                -webkit-appearance: none;
+                pointer-events: auto;
+            }
+
+            &::-moz-focus-outer {
+                border: 0;
+            }
+
+            &.xsmall {
+                &::-webkit-slider-runnable-track {
+                    height: global.$xsmall;
+                }
+
+                &::-webkit-slider-thumb {
+                    height: global.$xsmall;
+                    width: global.$xsmall;
+                    border-radius: global.$xsmall;
+                }
+            }
+
+            &.small {
+                &::-webkit-slider-runnable-track {
+                    height: global.$small;
+                }
+
+                &::-webkit-slider-thumb {
+                    height: global.$small;
+                    width: global.$small;
+                    border-radius: global.$small;
+                }
+            }
+
+            &.medium {
+                &::-webkit-slider-runnable-track {
+                    height: global.$medium;
+                }
+
+                &::-webkit-slider-thumb {
+                    height: global.$medium;
+                    width: global.$medium;
+                    border-radius: global.$medium;
+                }
+            }
+
+            &.large {
+                &::-webkit-slider-runnable-track {
+                    height: global.$large;
+                }
+
+                &::-webkit-slider-thumb {
+                    height: global.$large;
+                    width: global.$large;
+                    border-radius: global.$large;
+                }
+            }
+        }
     }
-  }
-  }
-  
 }
-
-
-
diff --git a/packages/components/src/components/Slider/Slider.tsx b/packages/components/src/components/Slider/Slider.tsx
index 3ca51efed..f6f53799c 100644
--- a/packages/components/src/components/Slider/Slider.tsx
+++ b/packages/components/src/components/Slider/Slider.tsx
@@ -1,178 +1,188 @@
-import React, { useEffect, useRef, useState } from 'react'
-import { Colors, getFontSize, getHeight, IGlobalProps, Size , getFormLabelSize, isDark, INumberProps } from '../../global'
-import './Slider.scss'
+import React, { useState } from 'react';
+import { Colors, getFontSize, getHeight, Size, getFormLabelSize, isDark, INumberProps } from '../../global';
+import './Slider.scss';
 
 export interface ISliderProps extends INumberProps {
-  multithumb: boolean
-  autorangeMinVal?: number // minimimum value that min can have when autoranging
-  autorangeMinSize?: number // minimum difference between min and max when autoranging
-  autorange?: number        // automatically adjust min/max to be +/- autorange/2 around the current value when the thumb is 15% from the min/max, or when the multithumbs are within 20% of the range and the range is bigger than autorange
-  endNumber?: number
-  setEndNumber?: (newVal: number) => void
-  setFinalNumber?: (newVal: number) => void
-  setFinalEndNumber?: (newVal: number) => void
-  decimals?: number; 
-  step?: number
-  minDiff?: number
+    multithumb: boolean;
+    autorangeMinVal?: number; // minimimum value that min can have when autoranging
+    autorangeMinSize?: number; // minimum difference between min and max when autoranging
+    autorange?: number; // automatically adjust min/max to be +/- autorange/2 around the current value when the thumb is 15% from the min/max, or when the multithumbs are within 20% of the range and the range is bigger than autorange
+    endNumber?: number;
+    setEndNumber?: (newVal: number) => void;
+    setFinalNumber?: (newVal: number) => void;
+    setFinalEndNumber?: (newVal: number) => void;
+    decimals?: number;
+    step?: number;
+    minDiff?: number;
 }
 
-let lastVal = 0;  // bcz: WHY do I have to do this??  the pointerdown event locks in the value of 'valLoc' when it's created so need some other way to get the current value to that old handler...
+let lastVal = 0; // bcz: WHY do I have to do this??  the pointerdown event locks in the value of 'valLoc' when it's created so need some other way to get the current value to that old handler...
 let lastEndVal = 0;
 
 export const Slider = (props: ISliderProps) => {
-  const [width, setWidth] = useState<number>(100);
-  const [valLoc, setNumberLoc] = useState<number>(props.number??(props.min + (props.max-props.min)/2));
-  const [endNumberLoc, setEndNumberLoc] = useState<number>(props.endNumber??(props.min + 2*(props.max-props.min)/3));
-  const [min, setMin] = useState<number>(props.min);
-  const [max, setMax] = useState<number>(props.max);
-  const { 
-    formLabel, 
-    formLabelPlacement, 
-    multithumb, 
-    autorange,
-    autorangeMinVal,
-    autorangeMinSize,
-    decimals,
-    step = 1, 
-    number = valLoc, 
-    endNumber = endNumberLoc, 
-    minDiff = (max-min)/20, 
-    size = Size.SMALL, 
-    height, 
-    unit, 
-    onPointerDown, 
-    setNumber, 
-    setEndNumber,
-    setFinalNumber, 
-    setFinalEndNumber,
-    color = Colors.MEDIUM_BLUE,
-    fillWidth
-  } = props
+    const [width, setWidth] = useState<number>(100);
+    const [valLoc, setNumberLoc] = useState<number>(props.number ?? props.min + (props.max - props.min) / 2);
+    const [endNumberLoc, setEndNumberLoc] = useState<number>(props.endNumber ?? props.min + (2 * (props.max - props.min)) / 3);
+    const [min, setMin] = useState<number>(props.min);
+    const [max, setMax] = useState<number>(props.max);
+    const {
+        formLabel,
+        formLabelPlacement,
+        multithumb,
+        autorange,
+        autorangeMinVal,
+        autorangeMinSize,
+        decimals,
+        step = 1,
+        number = valLoc,
+        endNumber = endNumberLoc,
+        minDiff = (max - min) / 20,
+        size = Size.SMALL,
+        height,
+        unit,
+        onPointerDown,
+        setNumber,
+        setEndNumber,
+        setFinalNumber,
+        setFinalEndNumber,
+        color = Colors.MEDIUM_BLUE,
+        fillWidth,
+    } = props;
 
-  const toDecimal = (num:number) => decimals !== undefined ? Math.round(num*Math.pow(10,decimals))/Math.pow(10,decimals): num;
+    const toDecimal = (num: number) => (decimals !== undefined ? Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals) : num);
 
-  const getLeftPos = (locVal: number) => {
-    const dragger = getHeight(height,size)
-    return (((locVal-min)/ (max-min)) * (width-dragger))
-  }
+    const getLeftPos = (locVal: number) => {
+        const dragger = getHeight(+(height || 0), size);
+        return ((locVal - min) / (max - min)) * (width - dragger);
+    };
 
-  const getValueLabel = (locVal: number): JSX.Element => {
-    return (<div className="rs-label-container" 
-            style={{
-              left: `${getLeftPos(locVal)}px`,
-              background: color,
-              color: isDark(color) ? Colors.LIGHT_GRAY : Colors.DARK_GRAY,
-              fontSize: getFontSize(size),
-              height: getHeight(height, size),
-              width: getHeight(height, size),
-              top: 0
-            }}
-            >
-            <span className="rs-label">
-              {toDecimal(locVal)}
-            </span>
-          </div>)
-  }
-  const checkAutorange = () => {
-    if (autorange) {
-      const minval = multithumb ? Math.min(lastVal, lastEndVal) : lastVal;
-      const maxval = multithumb ? Math.max(lastVal, lastEndVal) : lastVal;
-      const autosize = Math.max(autorangeMinSize??0,(autorange ?? (maxval-minval)))/2;
-      if ((Math.abs((minval - min)/(max-min)) < .15) || (Math.abs((max - maxval)/(max-min)) < .15) ||
-          (multithumb && maxval - minval < (max-min)/5 && autosize < max-min)
-       ) {
-        const newminval = autorangeMinVal !== undefined && minval-autosize < autorangeMinVal? autorangeMinVal : minval-autosize;
-        setMin(newminval)
-        setMax(newminval !== minval ? Math.max(maxval + autosize, newminval +autosize): maxval+autosize )
-      }
-    }
-  }
+    const getValueLabel = (locVal: number): JSX.Element => {
+        return (
+            <div
+                className="rs-label-container"
+                style={{
+                    left: `${getLeftPos(locVal)}px`,
+                    background: color,
+                    color: isDark(color) ? Colors.LIGHT_GRAY : Colors.DARK_GRAY,
+                    fontSize: getFontSize(size),
+                    height: getHeight(+(height || 0), size),
+                    width: getHeight(+(height || 0), size),
+                    top: 0,
+                }}>
+                <span className="rs-label">{toDecimal(locVal)}</span>
+            </div>
+        );
+    };
+    const checkAutorange = () => {
+        if (autorange) {
+            const minval = multithumb ? Math.min(lastVal, lastEndVal) : lastVal;
+            const maxval = multithumb ? Math.max(lastVal, lastEndVal) : lastVal;
+            const autosize = Math.max(autorangeMinSize ?? 0, autorange ?? maxval - minval) / 2;
+            if (Math.abs((minval - min) / (max - min)) < 0.15 || Math.abs((max - maxval) / (max - min)) < 0.15 || (multithumb && maxval - minval < (max - min) / 5 && autosize < max - min)) {
+                const newminval = autorangeMinVal !== undefined && minval - autosize < autorangeMinVal ? autorangeMinVal : minval - autosize;
+                setMin(newminval);
+                setMax(newminval !== minval ? Math.max(maxval + autosize, newminval + autosize) : maxval + autosize);
+            }
+        }
+    };
 
-  const valSlider = (which: string, val:number, onchange: (val:number) => void, setFinal: () => void) => {
-    const valPointerup = (e:PointerEvent) => {
-        document.removeEventListener('pointerup', valPointerup, true)
-        setFinal();
-        checkAutorange();
-    }
-    return (<div key={which} className={`range-slider ${size}`}>
-          {getValueLabel(val)}
-          <input
-            className={`rs-range ${size}`}
-            type="range"
-            color={color}
-            min={min}
-            max={max}
-            step={step}
-            value={val}
-            onPointerDown={e => document.addEventListener('pointerup', valPointerup, true)}
-            onChange={e => {
-              onchange(+e.target.value);
-              e.stopPropagation();
-            }}
-          />
-      </div>);
-  }
-  const onchange = (val:number) => {
-      if (autorangeMinVal && val < autorangeMinVal) val = autorangeMinVal;
-      setNumber?.(lastVal = Math.min(multithumb ? endNumber - (minDiff??0):Number.MAX_VALUE, val))
-      setNumberLoc(lastVal = Math.min(multithumb ? endNumber - (minDiff??0):Number.MAX_VALUE, val))
-  }
-  const onendchange = (val:number) => {
-    setEndNumber?.(lastEndVal = Math.max(number + (minDiff??0), val))
-    setEndNumberLoc(lastEndVal = Math.max(number + (minDiff??0), val))
-  }
-  const Slider:(JSX.Element|null)[] = [
-      !multithumb ? (null) : valSlider("end", endNumberLoc,onendchange, () => setFinalEndNumber?.(lastEndVal)),
-      valSlider("start", valLoc, onchange, () => setFinalNumber?.(lastVal))
-  ];
+    const valSlider = (which: string, val: number, onchange: (val: number) => void, setFinal: () => void) => {
+        const valPointerup = () => {
+            document.removeEventListener('pointerup', valPointerup, true);
+            setFinal();
+            checkAutorange();
+        };
+        return (
+            <div key={which} className={`range-slider ${size}`}>
+                {getValueLabel(val)}
+                <input
+                    className={`rs-range ${size}`}
+                    type="range"
+                    color={color}
+                    min={min}
+                    max={max}
+                    step={step}
+                    value={val}
+                    onPointerDown={() => document.addEventListener('pointerup', valPointerup, true)}
+                    onChange={e => {
+                        onchange(+e.target.value);
+                        e.stopPropagation();
+                    }}
+                />
+            </div>
+        );
+    };
+    const onchange = (val: number) => {
+        // eslint-disable-next-line no-param-reassign
+        if (autorangeMinVal && val < autorangeMinVal) val = autorangeMinVal;
+        setNumber?.((lastVal = Math.min(multithumb ? endNumber - (minDiff ?? 0) : Number.MAX_VALUE, val)));
+        setNumberLoc((lastVal = Math.min(multithumb ? endNumber - (minDiff ?? 0) : Number.MAX_VALUE, val)));
+    };
+    const onendchange = (val: number) => {
+        setEndNumber?.((lastEndVal = Math.max(number + (minDiff ?? 0), val)));
+        setEndNumberLoc((lastEndVal = Math.max(number + (minDiff ?? 0), val)));
+    };
+    const Slider: (JSX.Element | null)[] = [!multithumb ? null : valSlider('end', endNumberLoc, onendchange, () => setFinalEndNumber?.(lastEndVal)), valSlider('start', valLoc, onchange, () => setFinalNumber?.(lastVal))];
 
-  const slider: JSX.Element = (
-    <div className={`slider-wrapper`} 
-      onPointerEnter={e => {
-        lastVal = valLoc;
-        lastEndVal = endNumberLoc;
-      }}
-      style={{
-        padding: `5px 0px ${getHeight(height, size)}px 0px`,
-        width: fillWidth ? '100%' : 'fit-content'
-      }}>
-      <div className="slider-container" 
-            ref={r => {
-              r && new ResizeObserver(() => setWidth(+(r?.clientWidth??100))).observe(r);
-              setWidth(+(r?.clientWidth??100));
-            }} 
-            style={{height: getHeight(height, size)}}
-            onPointerDown={onPointerDown}
-      >
-          {Slider}
-          <div className="selected-range" style={{
-            height: getHeight(height, size) / 10,
-            background: multithumb ? Colors.LIGHT_GRAY : color
-
-          }}/>
-          <div className="range" style={{
-            height: getHeight(height, size) / 10,
-            width: getLeftPos(endNumber) - getLeftPos(number),
-            left: getLeftPos(number) + getHeight(height, size),
-            display: multithumb ? undefined: 'none',
-            background: color,
-          }}/>
-          <div className="box-minmax" style={{ fontSize: getFontSize(size), color }}>
-          <span>{toDecimal(min)}{unit}</span>
-          <span>{toDecimal(max)}{unit}</span>
+    const slider: JSX.Element = (
+        <div
+            className={`slider-wrapper`}
+            onPointerEnter={() => {
+                lastVal = valLoc;
+                lastEndVal = endNumberLoc;
+            }}
+            style={{
+                padding: `5px 0px ${getHeight(+(height || 0), size)}px 0px`,
+                width: fillWidth ? '100%' : 'fit-content',
+            }}>
+            <div
+                className="slider-container"
+                ref={r => {
+                    r && new ResizeObserver(() => setWidth(+(r?.clientWidth ?? 100))).observe(r);
+                    setWidth(+(r?.clientWidth ?? 100));
+                }}
+                style={{ height: getHeight(+(height || 0), size) }}
+                onPointerDown={onPointerDown}>
+                {Slider}
+                <div
+                    className="selected-range"
+                    style={{
+                        height: getHeight(+(height || 0), size) / 10,
+                        background: multithumb ? Colors.LIGHT_GRAY : color,
+                    }}
+                />
+                <div
+                    className="range"
+                    style={{
+                        height: getHeight(+(height || 0), size) / 10,
+                        width: getLeftPos(endNumber) - getLeftPos(number),
+                        left: getLeftPos(number) + getHeight(+(height || 0), size),
+                        display: multithumb ? undefined : 'none',
+                        background: color,
+                    }}
+                />
+                <div className="box-minmax" style={{ fontSize: getFontSize(size), color }}>
+                    <span>
+                        {toDecimal(min)}
+                        {unit}
+                    </span>
+                    <span>
+                        {toDecimal(max)}
+                        {unit}
+                    </span>
+                </div>
+            </div>
         </div>
-      </div>
-    </div>
-  )
-
-  return (
-    formLabel ? 
-    <div  className={`form-wrapper ${formLabelPlacement}`}>
-        <div className={'formLabel'} style={{fontSize: getFormLabelSize(size)}}>{formLabel}</div>
-        {slider}
-    </div>
-    :
-    slider
-)
-}
+    );
 
+    return formLabel ? (
+        <div className={`form-wrapper ${formLabelPlacement}`}>
+            <div className={'formLabel'} style={{ fontSize: getFormLabelSize(size) }}>
+                {formLabel}
+            </div>
+            {slider}
+        </div>
+    ) : (
+        slider
+    );
+};
diff --git a/packages/components/src/components/Template/Template.scss b/packages/components/src/components/Template/Template.scss
index c91147200..854abdca0 100644
--- a/packages/components/src/components/Template/Template.scss
+++ b/packages/components/src/components/Template/Template.scss
@@ -1,5 +1 @@
-@import '../../global/globalCssVariables.scss';
-
-.template-container {
-    
-}
\ No newline at end of file
+@use '../../global/globalCssVariables.scss' as global;
diff --git a/packages/components/src/components/Toggle/Toggle.scss b/packages/components/src/components/Toggle/Toggle.scss
index b2faa8d99..d65cb8e23 100644
--- a/packages/components/src/components/Toggle/Toggle.scss
+++ b/packages/components/src/components/Toggle/Toggle.scss
@@ -1,77 +1,77 @@
-@import '../../global/globalCssVariables.scss';
+@use '../../global/globalCssVariables.scss' as global;
 
 .toggle-label {
-  position: relative;
-  bottom: 0;
-  width: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  font-size: $xsmall-fontSize;
+    position: relative;
+    bottom: 0;
+    width: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-size: global.$xsmall-fontSize;
 }
 
 .toggle-container {
-  position: relative;
-  width: fit-content;
-  cursor: pointer;
-  overflow: hidden;
-  user-select: none;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  gap: 5px;
-  font-family: $default-font;
-  font-size: $medium-fontSize;
-  border-radius: 100px;
-  white-space: nowrap;
-  transition: 0.4s ease;
-  border: solid 1px;
-  border-color: $medium-blue;
+    position: relative;
+    width: fit-content;
+    cursor: pointer;
+    overflow: hidden;
+    user-select: none;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 5px;
+    font-family: global.$default-font;
+    font-size: global.$medium-fontSize;
+    border-radius: 100px;
+    white-space: nowrap;
+    transition: 0.4s ease;
+    border: solid 1px;
+    border-color: global.$medium-blue;
 
-  &:hover {
-    .toggle-background {
-      filter: opacity(0.2);
+    &:hover {
+        .toggle-background {
+            filter: opacity(0.2);
+        }
     }
-  }
 
-  &.switch {
-    &:hover {
-      .toggle-background {
-        filter: opacity(0);
-      }
+    &.switch {
+        &:hover {
+            .toggle-background {
+                filter: opacity(0);
+            }
+        }
     }
-  }
 
-  .toggle-content {
-    position: absolute;
-    display: flex;
-    align-items: center;
-    width: 100%;
-    height: 100%;
-    z-index: 1;
-    text-transform: uppercase;
-    font-family: Verdana, sans-serif;
-    font-weight: 500;
-    transition: 0.4s;
+    .toggle-content {
+        position: absolute;
+        display: flex;
+        align-items: center;
+        width: 100%;
+        height: 100%;
+        z-index: 1;
+        text-transform: uppercase;
+        font-family: Verdana, sans-serif;
+        font-weight: 500;
+        transition: 0.4s;
 
-    .toggle-switch {
-      background: $medium-blue;
-      transition: 0.4s;
-      border-radius: 100px;
+        .toggle-switch {
+            background: global.$medium-blue;
+            transition: 0.4s;
+            border-radius: 100px;
+        }
     }
-  }
 
-  .toggle-background {
-    width: 100%;
-    height: 100%;
-    z-index: 0;
-    position: absolute;
-    background: $medium-blue;
-    transition: 0.4s ease;
-    filter: opacity(0);
+    .toggle-background {
+        width: 100%;
+        height: 100%;
+        z-index: 0;
+        position: absolute;
+        background: global.$medium-blue;
+        transition: 0.4s ease;
+        filter: opacity(0);
 
-    &.active {
-      filter: opacity(0.4) !important;
+        &.active {
+            filter: opacity(0.4) !important;
+        }
     }
-  }
 }
diff --git a/packages/components/src/global/globalCssVariables.scss b/packages/components/src/global/globalCssVariables.scss
index 1ac2ef45c..ebc44106d 100644
--- a/packages/components/src/global/globalCssVariables.scss
+++ b/packages/components/src/global/globalCssVariables.scss
@@ -75,8 +75,10 @@ $standard-border-radius: 5px;
 
 // shadow
 $standard-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
-$standard-button-shadow: rgb(0 0 0 / 20%) 0px 2px 4px -1px,
-  rgb(0 0 0 / 14%) 0px 4px 5px 0px, rgb(0 0 0 / 12%) 0px 1px 10px 0px;
+$standard-button-shadow:
+    rgb(0 0 0 / 20%) 0px 2px 4px -1px,
+    rgb(0 0 0 / 14%) 0px 4px 5px 0px,
+    rgb(0 0 0 / 12%) 0px 1px 10px 0px;
 
 $dashboardselector-height: 32px;
 $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
@@ -91,70 +93,68 @@ $LEFT_MENU_WIDTH: 60px;
 $TREE_BULLET_WIDTH: 20px;
 
 :export {
-  contextMenuZindex: $contextMenu-zindex;
-  SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH;
-  COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
-  MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
-  MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
-  SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
-  ANTIMODEMENU_HEIGHT: $antimodemenu-height;
-  DASHBOARD_SELECTOR_HEIGHT: $dashboardselector-height;
-  DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
-  LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
-  TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
+    contextMenuZindex: $contextMenu-zindex;
+    SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH;
+    COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
+    MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
+    MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
+    SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
+    ANTIMODEMENU_HEIGHT: $antimodemenu-height;
+    DASHBOARD_SELECTOR_HEIGHT: $dashboardselector-height;
+    DFLT_IMAGE_NATIVE_DIM: $DFLT_IMAGE_NATIVE_DIM;
+    LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH;
+    TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH;
 }
 
 .form-wrapper {
-  display: flex;
-  flex-direction: column;
-  justify-content: flex-start;
-  align-items: flex-start;
-  gap: 0px;
-  padding-bottom: 5px;
-
-
-  .formLabel {
     display: flex;
-    font-family: $default-font;
-    text-transform: uppercase;
-    opacity: 0.8;
-    min-width: 'fit-content'
-  }
-
-  &.left {
-    flex-direction: row;
-    align-items: center;
-    gap: 3px;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-start;
+    gap: 0px;
+    padding-bottom: 5px;
 
     .formLabel {
-      text-align: left;
+        display: flex;
+        font-family: $default-font;
+        text-transform: uppercase;
+        opacity: 0.8;
+        min-width: 'fit-content';
     }
-  }
 
-  &.right {
-    flex-direction: row-reverse;
-    justify-content: flex-end;
-    align-items: center;
-    gap: 3px;
+    &.left {
+        flex-direction: row;
+        align-items: center;
+        gap: 3px;
 
-    .formLabel {
-      text-align: right;
+        .formLabel {
+            text-align: left;
+        }
     }
-  }
 
-  &.top {
-    flex-direction: column;
-    gap: 1px;
-  }
+    &.right {
+        flex-direction: row-reverse;
+        justify-content: flex-end;
+        align-items: center;
+        gap: 3px;
 
-  &.top-start {
-    flex-direction: column;
-    align-items: flex-start;
-  }
+        .formLabel {
+            text-align: right;
+        }
+    }
 
-  &.top-end {
-    flex-direction: column;
-    align-items: flex-end;
-  }
-}
+    &.top {
+        flex-direction: column;
+        gap: 1px;
+    }
 
+    &.top-start {
+        flex-direction: column;
+        align-items: flex-start;
+    }
+
+    &.top-end {
+        flex-direction: column;
+        align-items: flex-end;
+    }
+}
diff --git a/src/client/util/CaptureManager.scss b/src/client/util/CaptureManager.scss
index 8679a0101..e7cbb4287 100644
--- a/src/client/util/CaptureManager.scss
+++ b/src/client/util/CaptureManager.scss
@@ -1,5 +1,3 @@
-@import '../views/global/globalCssVariables.module';
-
 .capture-interface {
     //background-color: whitesmoke !important;
     width: 450px;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d2d7cabd2..60acd5b21 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -445,7 +445,6 @@ pie title Minerals in my tap water
             { toolTip: "Tap or drag to create a view slide",      title: "View Slide", icon: "address-card",       dragFactory: doc.emptyViewSlide as Doc,   clickFactory: DocCast(doc.emptyViewSlide), openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a data note",       title: "DataNote", icon: "window-maximize",      dragFactory: doc.emptyHeader as Doc,      clickFactory: DocCast(doc.emptyHeader),    openFactoryAsDelegate: true,           funcs: { hidden: "IsNoviceMode()"} },
             { toolTip: "Toggle a Calculator REPL",                title: "replviewer",  icon: "calculator",                                      clickFactory: '<ScriptingRepl />' as unknown as Doc, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
-           // { toolTip: "Toggle an UndoStack",                     title: "undostacker", icon: "calculator",                                      clickFactory: "<UndoStack />" as any,        openFactoryLocation: OpenWhere.overlay},
           ].map(tuple => (
               { openFactoryLocation: OpenWhere.addRight, 
                 scripts: { onClick:     'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', 
@@ -674,12 +673,13 @@ pie title Minerals in my tap water
                             CurrentUserUtils.createToolButton(opts), scripts, funcs);
         
         const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
-            { scripts: { onClick: "undo()"}, opts: { title: "Undo",              icon: "undo-alt",               toolTip: "Undo ⌘Z" }},
-            { scripts: { onClick: "redo()"}, opts: { title: "Redo",              icon: "redo-alt",               toolTip: "Redo ⌘⇧Z" }},
-            { scripts: { },                  opts: { title: "undoStack",         layout: "<UndoStack>",          toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet)
-            { scripts: { },                  opts: { title: "linker",            layout: "<LinkingUI>",          toolTip: "link started"}},
-            { scripts: { },                  opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
-            { scripts: { },                  opts: { title: "Branching",         layout: "<Branching>",          toolTip: "Branch, baby!"}}
+            { scripts: { onClick: "undo()"},  opts: { title: "Undo",              icon: "undo-alt",               toolTip: "Undo ⌘Z" }},
+            { scripts: { onClick: "redo()"},  opts: { title: "Redo",              icon: "redo-alt",               toolTip: "Redo ⌘⇧Z" }},
+            { scripts: { },                   opts: { title: "undoStack",         layout: "<UndoStack>",          toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet)
+            { scripts: { },                   opts: { title: "linker",            layout: "<LinkingUI>",          toolTip: "link started"}},
+            { scripts: { },                   opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
+            { scripts: { onClick: "hideUI()"},opts: { title: "Toggle UI",         icon: "portrait",               toolTip: "Toggle visibility of UI buttons"}},
+            { scripts: { },                   opts: { title: "Branching",         layout: "<Branching>",          toolTip: "Branch, baby!"}}
         ];
         const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts));
         const dockBtnsReqdOpts:DocumentOptions = {
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index dbfc48c63..f81f17589 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -1,5 +1,3 @@
-@import '../views/global/globalCssVariables.module';
-
 .settings-interface {
     //background-color: whitesmoke !important;
     width: 450px;
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 9d8a41844..3bbc297b8 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -16,6 +16,7 @@ export class SnappingManager {
     @observable _shiftKey = false;
     @observable _ctrlKey = false;
     @observable _metaKey = false;
+    @observable _hideUI = false;
     @observable _showPresPaths = false;
     @observable _isLinkFollowing = false;
     @observable _isDragging: boolean = false;
@@ -53,6 +54,7 @@ export class SnappingManager {
     public static get ShiftKey()       { return this.Instance._shiftKey; } // prettier-ignore
     public static get CtrlKey()        { return this.Instance._ctrlKey; } // prettier-ignore
     public static get MetaKey()        { return this.Instance._metaKey; } // prettier-ignore
+    public static get HideUI()         { return this.Instance._hideUI; } // prettier-ignore
     public static get ShowPresPaths()  { return this.Instance._showPresPaths; } // prettier-ignore
     public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore
     public static get IsDragging()     { return this.Instance._isDragging; } // prettier-ignore
@@ -73,6 +75,7 @@ export class SnappingManager {
     public static SetShiftKey       = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
     public static SetCtrlKey        = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore
     public static SetMetaKey        = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore
+    public static SetHideUI         = (vis: boolean) => runInAction(() => {this.Instance._hideUI = vis}); // prettier-ignore
     public static SetShowPresPaths  = (paths:boolean) => runInAction(() => {this.Instance._showPresPaths = paths}); // prettier-ignore
     public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => {this.Instance._isLinkFollowing = follow}); // prettier-ignore
     public static SetIsDragging     = (drag: boolean) => runInAction(() => {this.Instance._isDragging = drag}); // prettier-ignore
diff --git a/src/client/util/reportManager/ReportManager.scss b/src/client/util/reportManager/ReportManager.scss
index fd343ac8e..806741c22 100644
--- a/src/client/util/reportManager/ReportManager.scss
+++ b/src/client/util/reportManager/ReportManager.scss
@@ -1,4 +1,4 @@
-@import '../../views/global/globalCssVariables.module';
+@use '../../views/global/globalCssVariables.module' as global;
 
 // header
 
@@ -97,7 +97,7 @@
     background: transparent;
 
     // &:hover {
-    //     // border-bottom-color: $text-gray;
+    //     // border-bottom-color: global.$text-gray;
     // }
     // &:focus {
     //     // border-bottom-color: #4476f7;
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index 2ebf673fe..48fa86276 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -1,11 +1,11 @@
-@import './global/globalCssVariables.module';
+@use './global/globalCssVariables.module' as global;
 
 .antimodeMenu-cont {
     position: absolute;
     z-index: 10001;
-    height: $antimodemenu-height;
+    height: global.$antimodemenu-height;
     width: fit-content;
-    border-radius: $standard-border-radius;
+    border-radius: global.$standard-border-radius;
     overflow: hidden;
     // box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
     // border-radius: 0px 6px 6px 6px;
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 88e147a03..d22c4d096 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,9 +1,9 @@
-@import 'global/globalCssVariables.module.scss';
+@use 'global/globalCssVariables.module.scss' as global;
 
 .contextMenu-cont {
     position: absolute;
     display: flex;
-    z-index: $contextMenu-zindex;
+    z-index: global.$contextMenu-zindex;
     box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
     flex-direction: column;
     background: whitesmoke;
@@ -109,7 +109,7 @@
 .contextMenu-item:hover {
     border-width: 0.11px;
     border-style: none;
-    border-color: $medium-gray; //  rgb(187, 186, 186);
+    border-color: global.$medium-gray; //  rgb(187, 186, 186);
     border-bottom-style: solid;
     border-top-style: solid;
 
@@ -139,7 +139,7 @@
     transition: all 0.1s;
     border-width: 0.11px;
     border-style: none;
-    border-color: $medium-gray; //  rgb(187, 186, 186);
+    border-color: global.$medium-gray; //  rgb(187, 186, 186);
     // padding: 10px 0px 10px 0px;
     white-space: nowrap;
     font-size: 13px;
@@ -184,7 +184,7 @@
     .top-bar {
         height: 20px;
         width: 100%;
-        display: flex; 
+        display: flex;
 
         .close-menu {
             margin-top: 0;
@@ -200,7 +200,7 @@
         }
     }
 
-    .bottom-box{
+    .bottom-box {
         display: flex;
         flex-direction: row;
         justify-content: center;
@@ -209,11 +209,11 @@
         height: 100%;
         width: 100%;
 
-        .width-selector{
+        .width-selector {
             width: 100px;
         }
 
-        .max-min-selector{
+        .max-min-selector {
             height: 15px;
             width: 30px;
         }
diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss
index 25feca7bf..daa711bc4 100644
--- a/src/client/views/DashboardView.scss
+++ b/src/client/views/DashboardView.scss
@@ -1,4 +1,4 @@
-@import './global/globalCssVariables.module';
+@use './global/globalCssVariables.module' as global;
 
 $dashboard-left-menu-width: 250px;
 $dashboard-view-padding: 20px;
@@ -61,9 +61,9 @@ $dashboard-container-width: 250px;
     font-size: 120px;
     font-weight: 100;
     text-align: center;
-    border: solid 2px $light-gray;
+    border: solid 2px global.$light-gray;
     cursor: pointer;
-    color: $light-gray;
+    color: global.$light-gray;
     display: flex;
     display: flex;
     justify-content: center;
@@ -71,8 +71,8 @@ $dashboard-container-width: 250px;
     position: relative;
 
     &:hover {
-        color: $light-blue;
-        border: solid 2px $light-blue;
+        color: global.$light-blue;
+        border: solid 2px global.$light-blue;
     }
 
     .background {
@@ -91,14 +91,14 @@ $dashboard-container-width: 250px;
     cursor: pointer;
     width: $dashboard-container-width;
     height: $dashboard-container-height;
-    outline: solid 2px $light-gray;
+    outline: solid 2px global.$light-gray;
     outline-offset: -2px;
     display: flex;
     flex-direction: column;
     overflow: hidden;
 
     &:hover {
-        outline: solid 2px $light-blue;
+        outline: solid 2px global.$light-blue;
     }
 
     .title {
@@ -144,7 +144,7 @@ $dashboard-container-width: 250px;
 }
 
 .new-dashboard {
-    color: $dark-gray;
+    color: global.$dark-gray;
     padding: 10px;
     display: flex;
     width: 100%;
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index ede277aae..f19fecfa6 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables.module';
+@use 'global/globalCssVariables.module' as global;
 
 $linkGap: 3px;
 
@@ -7,13 +7,13 @@ $linkGap: 3px;
 }
 
 .documentButtonBar-linkButton-empty:hover {
-    background: $medium-gray;
+    background: global.$medium-gray;
     transform: scale(1.05);
     cursor: pointer;
 }
 
 .documentButtonBar-linkButton-nonempty:hover {
-    background: $medium-gray;
+    background: global.$medium-gray;
     transform: scale(1.05);
     cursor: pointer;
 }
@@ -32,7 +32,6 @@ $linkGap: 3px;
 
     .tags {
         width: 40px;
-
     }
 }
 .documentButtonBar-followTypes {
@@ -92,8 +91,8 @@ $linkGap: 3px;
     border-radius: 50%;
     opacity: 0.9;
     pointer-events: auto;
-    background-color: $dark-gray;
-    color: $white;
+    background-color: global.$dark-gray;
+    color: global.$white;
     text-transform: uppercase;
     letter-spacing: 2px;
     font-size: 75%;
@@ -104,7 +103,7 @@ $linkGap: 3px;
     align-items: center;
 
     &:hover {
-        background: $medium-gray;
+        background: global.$medium-gray;
         transform: scale(1.05);
         cursor: pointer;
     }
@@ -132,12 +131,12 @@ $linkGap: 3px;
     text-align: center;
     border-radius: 50%;
     pointer-events: auto;
-    background-color: $dark-gray;
+    background-color: global.$dark-gray;
     border: none;
     transition: 0.2s ease all;
 
     &:hover {
-        background-color: $medium-gray;
+        background-color: global.$medium-gray;
     }
 }
 
@@ -148,7 +147,7 @@ $linkGap: 3px;
     text-align: center;
     border-radius: 50%;
     pointer-events: auto;
-    background-color: $dark-gray;
+    background-color: global.$dark-gray;
     border: none;
     transition: 0.2s ease all;
     display: flex;
@@ -157,8 +156,8 @@ $linkGap: 3px;
     align-items: center;
 
     &:hover {
-        background-color: $black;
-        
+        background-color: global.$black;
+
         .documentButtonBar-pinTypes {
             display: flex;
         }
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 346df10d5..a5afb1305 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables.module';
+@use 'global/globalCssVariables.module' as global;
 
 $linkGap: 3px;
 $headerHeight: 20px;
@@ -195,14 +195,14 @@ $resizeHandler: 8px;
             .documentDecorations-titleSpan {
                 width: 100%;
                 border-radius: 8px;
-                background: $light-gray;
+                background: global.$contextMenu-zindex;
                 display: inline-block;
                 cursor: move;
             }
         }
 
         .documentDecorations-titleBackground {
-            background: $light-gray;
+            background: global.$light-gray;
             border-radius: 8px;
             width: 100%;
             height: 100%;
@@ -314,7 +314,7 @@ $resizeHandler: 8px;
     .documentDecorations-bottomResizer,
     .documentDecorations-rightResizer {
         pointer-events: auto;
-        background: $medium-gray-dim;
+        background: global.$medium-gray-dim;
         //opacity: 0.2;
         &:hover {
             opacity: 1;
@@ -344,7 +344,7 @@ $resizeHandler: 8px;
         border-radius: 100%;
         left: 7px;
         top: 7px;
-        background: $medium-gray;
+        background: global.$medium-gray;
         height: 10;
         width: 10;
         opacity: 0.5;
@@ -378,7 +378,7 @@ $resizeHandler: 8px;
         transform: translate(0px, -25%);
         padding-bottom: 100%;
         border-radius: 100%;
-        border: solid $medium-gray 10px;
+        border: solid global.$medium-gray 10px;
     }
 
     .documentDecorations-topLeftResizer,
@@ -497,13 +497,13 @@ $resizeHandler: 8px;
 }
 
 .linkButton-empty:hover {
-    background: $medium-gray;
+    background: global.$medium-gray;
     transform: scale(1.05);
     cursor: pointer;
 }
 
 .linkButton-nonempty:hover {
-    background: $medium-gray;
+    background: global.$medium-gray;
     transform: scale(1.05);
     cursor: pointer;
 }
@@ -520,7 +520,7 @@ $resizeHandler: 8px;
     align-items: center;
     gap: 5px;
     //top: 4px;
-    background: $light-gray;
+    background: global.$light-gray;
     opacity: 0.2;
     pointer-events: all;
     transition: opacity 1s;
@@ -542,8 +542,8 @@ $resizeHandler: 8px;
     text-align: center;
     border-radius: 50%;
     pointer-events: auto;
-    color: $dark-gray;
-    border: $dark-gray 1px solid;
+    color: global.$dark-gray;
+    border: global.$dark-gray 1px solid;
 }
 
 .linkButton-linker:hover {
@@ -558,8 +558,8 @@ $resizeHandler: 8px;
     border-radius: 50%;
     opacity: 0.9;
     pointer-events: auto;
-    background-color: $dark-gray;
-    color: $white;
+    background-color: global.$dark-gray;
+    color: global.$white;
     text-transform: uppercase;
     letter-spacing: 2px;
     font-size: 75%;
@@ -570,7 +570,7 @@ $resizeHandler: 8px;
     align-items: center;
 
     &:hover {
-        background: $medium-gray;
+        background: global.$medium-gray;
         transform: scale(1.05);
         cursor: pointer;
     }
@@ -600,13 +600,13 @@ $resizeHandler: 8px;
     border-radius: 50%;
     opacity: 0.9;
     font-size: 14;
-    background-color: $dark-gray;
-    color: $white;
+    background-color: global.$dark-gray;
+    color: global.$white;
     text-align: center;
     cursor: pointer;
 
     &:hover {
-        background: $medium-gray;
+        background: global.$medium-gray;
         transform: scale(1.05);
     }
 }
@@ -616,9 +616,9 @@ $resizeHandler: 8px;
     top: 25px;
     left: 0px;
     width: max-content;
-    font-family: $sans-serif;
+    font-family: global.$sans-serif;
     font-size: 12px;
-    background-color: $light-gray;
+    background-color: global.$light-gray;
     padding: 2px 12px;
     list-style: none;
 
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index e3df01bbb..0d3feb073 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -18,7 +18,7 @@ import { GestureOverlay } from './GestureOverlay';
 import './LightboxView.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
 import { OverlayView } from './OverlayView';
-import { DefaultStyleProvider, returnEmptyDocViewList, wavyBorderPath } from './StyleProvider';
+import { DefaultStyleProvider, returnEmptyDocViewList /* wavyBorderPath */ } from './StyleProvider';
 import { DocumentView } from './nodes/DocumentView';
 import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 import { StickerPalette } from './smartdraw/StickerPalette';
@@ -283,7 +283,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                         top: this.topBorder,
                         width: this.lightboxWidth(),
                         height: this.lightboxHeight(),
-                        clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
+                        // clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
                         background: SnappingManager.userBackgroundColor,
                     }}>
                     <GestureOverlay isActive>
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 02916e48e..bea1de435 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,5 +1,5 @@
-@import 'global/globalCssVariables.module';
-@import 'nodeModuleOverrides';
+@use 'global/globalCssVariables.module' as global;
+//  bcz: fix @import 'nodeModuleOverrides';
 
 :root {
     --flyoutHandleWidth: 28px;
@@ -10,8 +10,8 @@ body {
     width: 100%;
     height: 100%;
     overflow: hidden;
-    font-family: $sans-serif;
-    font-size: $body-text;
+    font-family: global.$sans-serif;
+    font-size: global.$body-text;
     margin: 0;
     position: absolute;
     top: 0;
@@ -54,7 +54,7 @@ button {
     background: black;
     outline: none;
     border: 0px;
-    color: $white;
+    color: global.$white;
     text-transform: uppercase;
     letter-spacing: 2px;
     font-size: 75%;
@@ -63,7 +63,7 @@ button {
 }
 
 button:hover {
-    background: $medium-gray;
+    background: global.$medium-gray;
     transform: scale(1.05);
     cursor: pointer;
 }
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index dda543470..3c1da88cc 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -87,10 +87,9 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
     await CurrentUserUtils.loadUserDocument(info);
     setTimeout(() => {
         // prevent zooming browser
-        document.getElementById('root')!.addEventListener('wheel', event => event.ctrlKey && event.preventDefault(), true);
+        document.getElementById('root')!.addEventListener('wheel', event => event.ctrlKey && event.preventDefault(), { capture: true, passive: true });
         const startload = (document as unknown as { startLoad: number }).startLoad; // see index.html in deploy/
         const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000);
-        console.log('Loading Time = ' + loading);
         const d = new Date();
         d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000);
         const expires = 'expires=' + d.toUTCString();
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index e204759ab..2170e0c34 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,5 +1,5 @@
-@import 'global/globalCssVariables.module.scss';
-@import 'nodeModuleOverrides';
+@use 'global/globalCssVariables.module.scss' as global;
+//  bcz: fix @import 'nodeModuleOverrides';
 html {
     overscroll-behavior-x: none;
 }
@@ -68,10 +68,10 @@ body {
 }
 
 .mainView-container {
-    color: $dark-gray;
+    color: global.$dark-gray;
 
     .lm_goldenlayout {
-        background: $medium-gray;
+        background: global.$medium-gray;
     }
 }
 
@@ -93,7 +93,7 @@ body {
 .mainView-propertiesDragger-minified,
 .mainView-propertiesDragger {
     //background-color: rgb(140, 139, 139);
-    background-color: $light-gray;
+    background-color: global.$light-gray;
     height: 55px;
     width: 17px;
     position: absolute;
@@ -133,10 +133,10 @@ body {
         flex-direction: column;
         position: relative;
         height: 100%;
-        background: $medium-gray;
+        background: global.$medium-gray;
 
         .documentView-node-topmost {
-            background: $light-gray;
+            background: global.$light-gray;
         }
     }
 
@@ -153,12 +153,12 @@ body {
 }
 
 .mainView-libraryHandle {
-    background-color: $light-gray;
+    background-color: global.$light-gray;
 }
 
 .mainView-leftMenuPanel {
     min-width: var(--menuPanelWidth);
-    border-right: $standard-border;
+    border-right: global.$standard-border;
 
     .collectionStackingView {
         scrollbar-width: none;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 195b1c572..8b0354471 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -99,7 +99,7 @@ export class MainView extends ObservableReactComponent<object> {
     @observable private _sidebarContent: Doc = Doc.MyLeftSidebarPanel;
     @observable private _leftMenuFlyoutWidth: number = 0;
     @computed get _hideUI() {
-        return this.mainDoc && this.mainDoc._type_collection !== CollectionViewType.Docking;
+        return SnappingManager.HideUI || (this.mainDoc && this.mainDoc._type_collection !== CollectionViewType.Docking);
     }
 
     @computed private get dashboardTabHeight() {
@@ -712,7 +712,7 @@ export class MainView extends ObservableReactComponent<object> {
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
                     suppressSetHeight
-                    renderDepth={this._hideUI ? 0 : -1}
+                    renderDepth={-1}
                 />
             </>
         );
@@ -1168,6 +1168,10 @@ ScriptingGlobals.add(function selectMainMenu(doc: Doc) {
     MainView.Instance.selectMenu(doc);
 });
 // eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function hideUI() {
+    SnappingManager.SetHideUI(!SnappingManager.HideUI);
+});
+// eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function createNewPresentation() {
     return MainView.Instance.createNewPresentation();
 }, 'creates a new presentation when called');
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss
index b8c73b6d3..6c2cda346 100644
--- a/src/client/views/PropertiesButtons.scss
+++ b/src/client/views/PropertiesButtons.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables.module.scss';
+@use 'global/globalCssVariables.module.scss' as global;
 
 $linkGap: 3px;
 
@@ -7,13 +7,13 @@ $linkGap: 3px;
 }
 
 .propertiesButtons-linkButton-empty:hover {
-    background: $medium-gray;
+    background: global.$medium-gray;
     transform: scale(1.05);
     cursor: pointer;
 }
 
 .propertiesButtons-linkButton-nonempty:hover {
-    background: $medium-gray;
+    background: global.$medium-gray;
     transform: scale(1.05);
     cursor: pointer;
 }
@@ -46,19 +46,19 @@ $linkGap: 3px;
     // margin-left: 4px;
 
     &:hover {
-        background: $medium-gray;
+        background: global.$medium-gray;
         transform: scale(1.05);
         cursor: pointer;
     }
 }
 .propertiesButtons-linkButton-empty.toggle-on {
-    background-color: $medium-blue;
-    color: $white;
+    background-color: global.$medium-blue;
+    color: global.$white;
     width: 100%;
 }
 .propertiesButtons-linkButton-empty.toggle-hover {
-    background-color: $light-blue;
-    color: $black;
+    background-color: global.$light-blue;
+    color: global.$black;
     width: 100%;
 }
 .propertiesButtons-linkButton-empty.toggle-off {
@@ -88,7 +88,7 @@ $linkGap: 3px;
     cursor: pointer;
     text-align: center;
     margin-top: 5px;
-    border: 0.5px solid $medium-gray;
+    border: 0.5px solid global.$medium-gray;
     background-color: rgb(230, 230, 230);
     border-radius: 5px;
     padding: 4px;
@@ -111,7 +111,7 @@ $linkGap: 3px;
 
     .list-item {
         cursor: pointer;
-        color: $black;
+        color: global.$black;
         width: 100%;
         height: 25px;
         font-weight: 400;
diff --git a/src/client/views/PropertiesSection.scss b/src/client/views/PropertiesSection.scss
index d32da1bf1..f7138dd50 100644
--- a/src/client/views/PropertiesSection.scss
+++ b/src/client/views/PropertiesSection.scss
@@ -1,5 +1,3 @@
-@import './global/globalCssVariables.module.scss';
-
 .propertiesView-section {
     .propertiesView-content {
         padding: 10px;
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 7866e67e7..280de4893 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -1,4 +1,4 @@
-@import './global/globalCssVariables.module.scss';
+@use './global/globalCssVariables.module.scss' as global;
 
 .propertiesView-presentationTrails-title {
     display: flex;
@@ -28,7 +28,7 @@
     font-family: 'Roboto';
     font-size: 12px;
     cursor: auto;
-    border-left: $standard-border;
+    border-left: global.$standard-border;
 
     .slider-text {
         font-size: 8px;
@@ -567,7 +567,7 @@
     height: fit-content;
 
     &:hover {
-        border: 0.75px solid $medium-blue;
+        border: 0.75px solid global.$medium-blue;
     }
 }
 
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
index 36a9ce6d0..8879fc20d 100644
--- a/src/client/views/TemplateMenu.scss
+++ b/src/client/views/TemplateMenu.scss
@@ -1,4 +1,4 @@
-@import 'global/globalCssVariables.module.scss';
+@use 'global/globalCssVariables.module.scss' as global;
 .templating-menu {
     position: absolute;
     pointer-events: auto;
@@ -24,15 +24,15 @@
     cursor: pointer;
 
     &:hover {
-        background: $medium-gray;
+        background: global.$medium-gray;
         transform: scale(1.05);
     }
 }
 
 .template-list {
-    font-family: $sans-serif;
+    font-family: global.$sans-serif;
     font-size: 12px;
-    background-color: $light-gray;
+    background-color: global.$light-gray;
     padding: 2px 12px;
     list-style: none;
     position: relative;
diff --git a/src/client/views/animationtimeline/Region.scss b/src/client/views/animationtimeline/Region.scss
index b390ae34e..df82febea 100644
--- a/src/client/views/animationtimeline/Region.scss
+++ b/src/client/views/animationtimeline/Region.scss
@@ -1,4 +1,4 @@
-@import './../global/globalCssVariables.module.scss';
+@use './../global/globalCssVariables.module.scss' as global;
 
 $timelineColor: #9acedf;
 $timelineDark: #77a1aa;
@@ -14,11 +14,11 @@ $timelineDark: #77a1aa;
         height: 200px;
         top: 50%;
         position: relative;
-        background-color: $white;
+        background-color: global.$white;
 
         .menutable {
             tr:nth-child(odd) {
-                background-color: $light-gray;
+                background-color: global.$light-gray;
             }
         }
     }
@@ -70,7 +70,7 @@ $timelineDark: #77a1aa;
         height: 100%;
         position: absolute;
         pointer-events: none;
-        background: linear-gradient(to left, $timelineColor 10%, $white);
+        background: linear-gradient(to left, $timelineColor 10%, global.$white);
     }
 
     .fadeRight {
@@ -78,7 +78,7 @@ $timelineDark: #77a1aa;
         height: 100%;
         position: absolute;
         pointer-events: none;
-        background: linear-gradient(to right, $timelineColor 10%, $white);
+        background: linear-gradient(to right, $timelineColor 10%, global.$white);
     }
 
     .divider {
diff --git a/src/client/views/animationtimeline/Timeline.scss b/src/client/views/animationtimeline/Timeline.scss
index 35ba0fa7f..e1d3b190c 100644
--- a/src/client/views/animationtimeline/Timeline.scss
+++ b/src/client/views/animationtimeline/Timeline.scss
@@ -1,4 +1,4 @@
-@import './../global/globalCssVariables.module.scss';
+@use './../global/globalCssVariables.module.scss' as global;
 
 $timelineColor: #9acedf;
 $timelineDark: #77a1aa;
@@ -159,7 +159,7 @@ $timelineDark: #77a1aa;
     width: 100%;
     height: 300px;
     position: absolute;
-    background-color: $light-gray;
+    background-color: global.$light-gray;
     border-bottom: 2px solid $timelineDark;
     transition: transform 500ms ease;
 
@@ -247,7 +247,7 @@ $timelineDark: #77a1aa;
             top: 0px;
             width: 100px;
             height: 30%;
-            border: 1px solid $dark-gray;
+            border: 1px solid global.$dark-gray;
             font-size: 12px;
             line-height: 11px;
             background-color: $timelineDark;
diff --git a/src/client/views/animationtimeline/TimelineMenu.scss b/src/client/views/animationtimeline/TimelineMenu.scss
index de2042f17..5398a4a97 100644
--- a/src/client/views/animationtimeline/TimelineMenu.scss
+++ b/src/client/views/animationtimeline/TimelineMenu.scss
@@ -1,9 +1,9 @@
-@import './../global/globalCssVariables.module.scss';
+@use './../global/globalCssVariables.module.scss' as global;
 
 .timeline-menu-container {
     position: absolute;
     display: flex;
-    box-shadow: $medium-gray 0.2vw 0.2vw 0.4vw;
+    box-shadow: global.$medium-gray 0.2vw 0.2vw 0.4vw;
     flex-direction: column;
     background: whitesmoke;
     z-index: 10000;
@@ -14,7 +14,7 @@
     border: solid #bbbbbbbb 1px;
 
     .timeline-menu-input {
-        font: $sans-serif;
+        font: global.$sans-serif;
         font-size: 13px;
         width: 100%;
         text-transform: uppercase;
@@ -33,11 +33,11 @@
         border-top-left-radius: 15px;
         border-top-right-radius: 15px;
         text-transform: uppercase;
-        background: $dark-gray;
+        background: global.$dark-gray;
         letter-spacing: 2px;
 
         .timeline-menu-header-desc {
-            font: $sans-serif;
+            font: global.$sans-serif;
             font-size: 13px;
             text-align: center;
             color: whitesmoke;
@@ -72,15 +72,15 @@
     .timeline-menu-item:hover {
         border-width: 0.11px;
         border-style: none;
-        border-color: $medium-gray;
+        border-color: global.$medium-gray;
         border-bottom-style: solid;
         border-top-style: solid;
-        background: $medium-blue;
+        background: global.$medium-blue;
     }
 
     .timeline-menu-desc {
         padding-left: 10px;
-        font: $sans-serif;
+        font: global.$sans-serif;
         font-size: 13px;
     }
 }
diff --git a/src/client/views/animationtimeline/TimelineOverview.scss b/src/client/views/animationtimeline/TimelineOverview.scss
index 2878232e6..8336f2b2f 100644
--- a/src/client/views/animationtimeline/TimelineOverview.scss
+++ b/src/client/views/animationtimeline/TimelineOverview.scss
@@ -1,4 +1,4 @@
-@import './../global/globalCssVariables.module.scss';
+@use './../global/globalCssVariables.module.scss' as global;
 
 $timelineColor: #9acedf;
 $timelineDark: #77a1aa;
diff --git a/src/client/views/animationtimeline/Track.scss b/src/client/views/animationtimeline/Track.scss
index f56b2fe5f..7f5e8b8f3 100644
--- a/src/client/views/animationtimeline/Track.scss
+++ b/src/client/views/animationtimeline/Track.scss
@@ -1,12 +1,12 @@
-@import './../global/globalCssVariables.module.scss';
+@use './../global/globalCssVariables.module.scss' as global;
 
 .track-container {
     .track {
         .inner {
             top: 0px;
             width: calc(100%);
-            background-color: $white;
-            border: 1px solid $dark-gray;
+            background-color: global.$white;
+            border: 1px solid global.$dark-gray;
             position: relative;
             z-index: 100;
         }
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 79c53db08..06dd4627f 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -1,5 +1,3 @@
-@import '../global/globalCssVariables.module.scss';
-
 .collectionCardView-outer {
     height: 100%;
     width: 100%;
diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss
index 42e112906..13e6b54c2 100644
--- a/src/client/views/collections/CollectionCarousel3DView.scss
+++ b/src/client/views/collections/CollectionCarousel3DView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 .collectionCarousel3DView-outer {
     height: 100%;
     position: relative;
@@ -10,8 +10,8 @@
 .carousel-wrapper {
     display: flex;
     position: absolute;
-    top: $CAROUSEL3D_TOP * 1%;
-    height: ($CAROUSEL3D_SIDE_SCALE * 100) * 1%;
+    top: global.$CAROUSEL3D_TOP * 1%;
+    height: (global.$CAROUSEL3D_SIDE_SCALE * 100) * 1%;
     align-items: center;
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 
@@ -24,7 +24,7 @@
         pointer-events: none;
         opacity: 0.5;
         z-index: 1;
-        transform: scale($CAROUSEL3D_SIDE_SCALE);
+        transform: scale(global.$CAROUSEL3D_SIDE_SCALE);
         user-select: none;
     }
 
@@ -32,7 +32,7 @@
         pointer-events: unset;
         opacity: 1;
         z-index: 2;
-        transform: scale($CAROUSEL3D_CENTER_SCALE);
+        transform: scale(global.$CAROUSEL3D_CENTER_SCALE);
     }
 }
 
@@ -80,7 +80,7 @@
 .carousel3DView-back {
     top: 0;
     background: transparent;
-    width: calc((1 - #{$CAROUSEL3D_CENTER_SCALE} * 0.33) / 2 * 100%);
+    width: calc((1 - #{global.$CAROUSEL3D_CENTER_SCALE} * 0.33) / 2 * 100%);
     height: 100%;
 }
 
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index a747ef45f..7c19d39da 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .lm_root {
     position: relative;
@@ -285,7 +285,7 @@
     background: transparent;
     border: solid 0px transparent;
     cursor: grab;
-    color: $black;
+    color: global.$black;
 }
 .collectiondockingview-container .lm_splitter {
     opacity: 0.2;
@@ -378,7 +378,7 @@ ul.lm_tabs::before {
     z-index: 1;
     text-align: center;
     font-size: 18;
-    color: $dark-gray;
+    color: global.$dark-gray;
 
     img {
         position: relative;
@@ -491,7 +491,7 @@ ul.lm_tabs::before {
     }
 
     .lm_content {
-        background: $white;
+        background: global.$white;
     }
 
     .lm_controls {
@@ -557,7 +557,7 @@ ul.lm_tabs::before {
     }
 
     .flexlayout__splitter {
-        background-color: $dark-gray;
+        background-color: global.$dark-gray;
     }
 
     .flexlayout__splitter:hover {
@@ -626,7 +626,7 @@ ul.lm_tabs::before {
         position: absolute;
         box-sizing: border-box;
         background-color: #222;
-        color: $dark-gray;
+        color: global.$dark-gray;
     }
 
     .flexlayout__tab_button {
@@ -709,7 +709,7 @@ ul.lm_tabs::before {
     }
 
     .flexlayout__tab_header_outer {
-        background-color: $dark-gray;
+        background-color: global.$dark-gray;
         position: absolute;
         left: 0;
         right: 0;
@@ -769,28 +769,28 @@ ul.lm_tabs::before {
     }
 
     .flexlayout__border_top {
-        background-color: $dark-gray;
+        background-color: global.$dark-gray;
         border-bottom: 1px solid #ddd;
         box-sizing: border-box;
         overflow: hidden;
     }
 
     .flexlayout__border_bottom {
-        background-color: $dark-gray;
+        background-color: global.$dark-gray;
         border-top: 1px solid #333;
         box-sizing: border-box;
         overflow: hidden;
     }
 
     .flexlayout__border_left {
-        background-color: $dark-gray;
+        background-color: global.$dark-gray;
         border-right: 1px solid #333;
         box-sizing: border-box;
         overflow: hidden;
     }
 
     .flexlayout__border_right {
-        background-color: $dark-gray;
+        background-color: global.$dark-gray;
         border-left: 1px solid #333;
         box-sizing: border-box;
         overflow: hidden;
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index 45d9394ed..11fce720c 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,13 +1,13 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .collectionMenu-container {
     display: flex;
     position: relative;
     align-content: center;
     justify-content: space-between;
-    background-color: $dark-gray;
+    background-color: global.$dark-gray;
     height: 40px;
-    border-bottom: $standard-border;
+    border-bottom: global.$standard-border;
     padding: 0 10px;
     align-items: center;
     overflow-x: auto;
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index 95fda7b0a..0d24a56b5 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .collectionNoteTakingView-DocumentButtons {
     opacity: 0;
@@ -58,7 +58,7 @@
 .documentButtonMenu {
     position: relative;
     height: fit-content;
-    border-bottom: $standard-border;
+    border-bottom: global.$standard-border;
     display: flex;
     justify-content: center;
     flex-direction: column;
@@ -70,11 +70,11 @@
         width: 90%;
         margin: 5px;
         font-size: 11px;
-        background-color: $light-blue;
-        color: $medium-blue;
+        background-color: global.$light-blue;
+        color: global.$medium-blue;
         padding: 10px;
         border-radius: 10px;
-        border: solid 2px $medium-blue;
+        border: solid 2px global.$medium-blue;
     }
 }
 
@@ -146,9 +146,9 @@
         padding: 10px;
         height: 2vw;
         width: 100%;
-        font-family: $sans-serif;
-        background: $dark-gray;
-        color: $white;
+        font-family: global.$sans-serif;
+        background: global.$dark-gray;
+        color: global.$white;
     }
 
     .collectionNoteTakingView-columnDragger {
@@ -206,7 +206,7 @@
         margin-left: 2px;
         margin-right: 2px;
         margin-top: 2px;
-        background: $medium-gray;
+        background: global.$medium-gray;
         height: 5px;
 
         &.active {
@@ -258,7 +258,7 @@
         text-align: center;
         margin: auto;
         margin-bottom: 10px;
-        background: $medium-gray;
+        background: global.$medium-gray;
         // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong
 
         .editableView-input:hover,
@@ -279,7 +279,7 @@
             display: flex;
             align-items: center;
             justify-content: center;
-            color: $dark-gray;
+            color: global.$dark-gray;
 
             .editableView-container-editing-oneLine,
             .editableView-container-editing {
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index 0ced3f9e3..d05c0ffde 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .collectionStackedTimeline-timelineContainer {
     height: 100%;
@@ -6,7 +6,7 @@
     overflow-x: auto;
     overflow-y: hidden;
     border: none;
-    background-color: $white;
+    background-color: global.$white;
     border-width: 0 2px 0 2px;
 
     &:hover {
@@ -28,7 +28,7 @@
 
 .collectionStackedTimeline {
     position: absolute;
-    background: $off-white;
+    background: global.$off-white;
     z-index: 1000;
     height: 100%;
     overflow: hidden;
@@ -36,7 +36,7 @@
     .collectionStackedTimeline-trim-shade {
         position: absolute;
         height: 100%;
-        background-color: $dark-gray;
+        background-color: global.$dark-gray;
         opacity: 0.3;
         top: 0;
     }
@@ -45,7 +45,7 @@
         height: 100%;
         position: absolute;
         box-sizing: border-box;
-        border: 2px solid $medium-blue;
+        border: 2px solid global.$medium-blue;
         display: flex;
         justify-content: space-between;
         max-width: 100%;
@@ -53,7 +53,7 @@
         left: 0;
 
         .collectionStackedTimeline-trim-handle {
-            background-color: $medium-blue;
+            background-color: global.$medium-blue;
             height: 100%;
             width: 5px;
             cursor: ew-resize;
@@ -65,12 +65,12 @@
         width: 10px;
         top: 2.5%;
         height: 95%;
-        background: $light-blue;
+        background: global.$light-blue;
         border-radius: 3px;
         opacity: 0.3;
         z-index: 500;
         border-style: solid;
-        border-color: $medium-blue;
+        border-color: global.$medium-blue;
         border-width: 1px;
     }
 
@@ -84,12 +84,12 @@
     }
 
     .collectionStackedTimeline-current {
-        background-color: $pink;
+        background-color: global.$pink;
     }
 
     .collectionStackedTimeline-hover {
         display: none;
-        background-color: $medium-blue;
+        background-color: global.$medium-blue;
     }
 
     .collectionStackedTimeline-marker-timeline {
@@ -97,14 +97,14 @@
         top: 2.5%;
         height: 95%;
         border-radius: 4px;
-        //background: $light-gray;
+        //background: global.$light-gray;
         &:hover {
             opacity: 1;
         }
 
         .collectionStackedTimeline-left-resizer,
         .collectionStackedTimeline-resizer {
-            background: $dark-gray;
+            background: global.$dark-gray;
             position: absolute;
             top: 0;
             height: 100%;
@@ -141,7 +141,7 @@
 
     .hoverTime {
         position: absolute;
-        color: $dark-gray;
+        color: global.$dark-gray;
         transform: translate(0, -100%);
 
         font-weight: bold;
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 6400a0a8e..5237bdffb 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .collectionMasonryView {
     display: inline;
@@ -18,7 +18,7 @@
 .documentButtonMenu {
     position: relative;
     height: fit-content;
-    border-bottom: $standard-border;
+    border-bottom: global.$standard-border;
     display: flex;
     justify-content: center;
     flex-direction: column;
@@ -30,10 +30,10 @@
         width: 90%;
         margin: 5px;
         font-size: 11px;
-        color: $medium-blue;
+        color: global.$medium-blue;
         padding: 10px;
         border-radius: 5px;
-        border: solid 0.5px $medium-blue;
+        border: solid 0.5px global.$medium-blue;
     }
 }
 
@@ -115,9 +115,9 @@
         padding: 10px;
         height: 2vw;
         width: 100%;
-        font-family: $sans-serif;
-        background: $dark-gray;
-        color: $white;
+        font-family: global.$sans-serif;
+        background: global.$dark-gray;
+        color: global.$white;
     }
 
     .collectionStackingView-columnDragger {
@@ -149,7 +149,7 @@
 
     .collectionStackingView-collapseBar {
         margin-top: 2px;
-        background: $medium-gray;
+        background: global.$medium-gray;
         height: 5px;
         width: 100%;
         display: none;
@@ -207,11 +207,11 @@
         text-align: center;
         margin: auto;
         margin-bottom: 10px;
-        background: $medium-gray;
+        background: global.$medium-gray;
         // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong
 
         .editableView-input {
-            color: $dark-gray;
+            color: global.$dark-gray;
         }
 
         .editableView-input:hover,
@@ -232,7 +232,7 @@
             display: flex;
             align-items: center;
             justify-content: center;
-            color: $dark-gray;
+            color: global.$dark-gray;
 
             .editableView-container-editing-oneLine,
             .editableView-container-editing {
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index bbbef78b4..2a03ea708 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .collectionTreeView-container {
     transform-origin: top left;
@@ -12,7 +12,7 @@
     width: 100%;
     position: relative;
     top: 0;
-    // background: $light-gray;
+    // background: global.$light-gray;
     font-size: 13px;
     overflow: auto;
     user-select: none;
@@ -21,7 +21,7 @@
 
     ul {
         list-style: none;
-        padding-left: $TREE_BULLET_WIDTH;
+        padding-left: global.$TREE_BULLET_WIDTH;
         margin-bottom: 1px; // otherwise vertical scrollbars may pop up for no apparent reason....
         > .contentFittingDocumentView {
             width: unset;
@@ -47,7 +47,7 @@
     }
 
     .delete-button {
-        color: $medium-gray;
+        color: global.$medium-gray;
         // float: right;
         margin-left: 15px;
         // margin-top: 3px;
@@ -90,7 +90,7 @@
 .collectionTreeView-subtitle {
     font-style: italic;
     font-size: 8pt;
-    color: $medium-gray;
+    color: global.$medium-gray;
 }
 
 .docContainer {
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index de53a2c62..06c324bd0 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -1,10 +1,10 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .collectionView {
     border-width: 0;
-    border-color: $light-gray;
+    border-color: global.$light-gray;
     border-style: solid;
-    border-radius: 0 0 $border-radius $border-radius;
+    border-radius: 0 0 global.$border-radius global.$border-radius;
     box-sizing: border-box;
     border-radius: inherit;
     width: 100%;
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index dd4c0b881..397e35ca9 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .tabDocView-content {
     height: 100%;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 2ab1a5ac1..78794d112 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .treeView-label {
     max-height: 1.5em;
@@ -14,7 +14,7 @@
     .bullet-outline {
         position: relative;
         width: fit-content;
-        color: $medium-gray;
+        color: global.$medium-gray;
         transform: scale(0.5);
         display: inline-flex;
         align-items: center;
@@ -66,7 +66,7 @@
         min-height: 20px;
         min-width: 15px;
         margin-right: 3px;
-        color: $medium-gray;
+        color: global.$medium-gray;
         border: #80808030 1px solid;
         border-radius: 5px;
         z-index: 1;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 46bd37f6d..cce0ff684 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.module.scss';
+@use '../../global/globalCssVariables.module.scss' as global;
 
 .collectionfreeformview-none {
     position: inherit;
@@ -32,9 +32,9 @@
 .collectionfreeformview-mask-empty,
 .collectionfreeformview-mask {
     z-index: 5000;
-    width: $INK_MASK_SIZE;
-    height: $INK_MASK_SIZE;
-    transform: translate($INK_MASK_SIZE_HALF, $INK_MASK_SIZE_HALF);
+    width: global.$INK_MASK_SIZE;
+    height: global.$INK_MASK_SIZE;
+    transform: translate(global.$INK_MASK_SIZE_HALF, global.$INK_MASK_SIZE_HALF);
     pointer-events: none;
     position: absolute;
     background-color: transparent;
@@ -211,11 +211,11 @@
 
     //nested freeform views
     // .collectionfreeformview-container {
-    // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
-    //   linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
+    // background-image: linear-gradient(to right, global.$light-color-secondary 1px, transparent 1px),
+    //   linear-gradient(to bottom, global.$light-color-secondary 1px, transparent 1px);
     // background-size: 30px 30px;
     // }
-    border: 0px solid $light-gray;
+    border: 0px solid global.$light-gray;
     border-radius: inherit;
     box-sizing: border-box;
     position: absolute;
@@ -233,7 +233,7 @@
     box-sizing: border-box;
     width: 98%;
     height: 98%;
-    border-radius: $border-radius;
+    border-radius: global.$border-radius;
 }
 
 //this is an animation for the blinking cursor!
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelHandler.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelHandler.tsx
index f050b9846..1a44e094d 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelHandler.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelHandler.tsx
@@ -9,7 +9,8 @@ import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 import './ImageLabelHandler.scss';
 
 @observer
-export class ImageLabelHandler extends ObservableReactComponent<{}> {
+export class ImageLabelHandler extends ObservableReactComponent<object> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: ImageLabelHandler;
 
     @observable _display: boolean = false;
@@ -19,11 +20,10 @@ export class ImageLabelHandler extends ObservableReactComponent<{}> {
     @observable _currentLabel: string = '';
     @observable _labelGroups: string[] = [];
 
-    constructor(props: any) {
+    constructor(props: object) {
         super(props);
         makeObservable(this);
         ImageLabelHandler.Instance = this;
-        console.log('Instantiated label handler!');
     }
 
     @action
@@ -41,8 +41,8 @@ export class ImageLabelHandler extends ObservableReactComponent<{}> {
     };
 
     @action
-    addLabel = (label: string) => {
-        label = label.toUpperCase().trim();
+    addLabel = (labelIn: string) => {
+        const label = labelIn.toUpperCase().trim();
         if (label.length > 0) {
             if (!this._labelGroups.includes(label)) {
                 this._labelGroups = [...this._labelGroups, label];
@@ -96,10 +96,10 @@ export class ImageLabelHandler extends ObservableReactComponent<{}> {
                     <div>
                         {this._labelGroups.map(group => {
                             return (
-                                <div>
+                                <div key={group}>
                                     <p>{group}</p>
                                     <IconButton
-                                        tooltip={'Remove Label'}
+                                        tooltip="Remove Label"
                                         onPointerDown={() => {
                                             this.removeLabel(group);
                                         }}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index b8ceec139..0dfaed38a 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -1,12 +1,12 @@
-@import '../../global/globalCssVariables.module.scss';
-@import '../../_nodeModuleOverrides';
+@use '../../global/globalCssVariables.module.scss' as global;
+// bcz fix   @import '../../_nodeModuleOverrides';
 
 .collectionLinearView {
     width: 100%;
 }
 .collectionLinearView-label {
     color: black;
-    background-color: $light-gray;
+    background-color: global.$light-gray;
     width: 100%;
 }
 .collectionLinearView-outer {
@@ -32,8 +32,8 @@
         }
 
         > span {
-            background: $dark-gray;
-            color: $white;
+            background: global.$dark-gray;
+            color: global.$white;
             border-radius: 18px;
             margin-right: 6px;
             cursor: pointer;
@@ -44,7 +44,7 @@
         }
 
         .bottomPopup-background {
-            background: $medium-blue;
+            background: global.$medium-blue;
             display: flex;
             border-radius: 10px;
             height: 35;
@@ -55,7 +55,7 @@
         }
 
         .bottomPopup-text {
-            color: $white;
+            color: global.$white;
             display: inline;
             white-space: nowrap;
             padding-left: 8px;
@@ -72,7 +72,7 @@
             padding-left: 8px;
             padding-right: 8px;
             vertical-align: middle;
-            background-color: $light-gray;
+            background-color: global.$light-gray;
             border-radius: 3px;
             color: black;
             margin-right: 5px;
@@ -86,7 +86,7 @@
             padding-left: 8px;
             padding-right: 8px;
             vertical-align: middle;
-            background-color: $close-red;
+            background-color: global.$close-red;
             border-radius: 3px;
             color: black;
         }
@@ -94,13 +94,13 @@
         > label {
             pointer-events: all;
             cursor: pointer;
-            background-color: $medium-blue;
+            background-color: global.$medium-blue;
             padding: 5;
             border-radius: 2px;
             height: 100%;
             min-width: 25;
             margin: 0;
-            color: $white;
+            color: global.$white;
             display: flex;
             font-weight: 100;
             transition: transform 0.2s;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 817ceac97..0bf78f57c 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.module.scss';
+@use '../../global/globalCssVariables.module.scss' as global;
 
 .collectionSchemaView {
     cursor: default;
@@ -7,7 +7,7 @@
     flex-direction: row;
 
     .schema-table {
-        background-color: $white;
+        background-color: global.$white;
         cursor: grab;
         width: 100%;
 
@@ -49,10 +49,10 @@
 
 .schema-column-menu,
 .schema-filter-menu {
-    background: $light-gray;
+    background: global.$light-gray;
     position: absolute;
-    border: 1px solid $medium-gray;
-    border-bottom: 2px solid $medium-gray;
+    border: 1px solid global.$medium-gray;
+    border-bottom: 2px solid global.$medium-gray;
     max-height: 201px;
     display: flex;
     overflow: hidden;
@@ -66,7 +66,7 @@
         width: 100%;
 
         &:hover {
-            background-color: $medium-gray;
+            background-color: global.$medium-gray;
         }
         .schema-search-result-type {
             font-family: 'Courier New', Courier, monospace;
@@ -74,8 +74,8 @@
 
         .schema-search-result-type,
         .schema-search-result-desc {
-            color: $dark-gray;
-            font-size: $body-text;
+            color: global.$dark-gray;
+            font-size: global.$body-text;
         }
         .schema-search-result-desc {
             font-style: italic;
@@ -120,9 +120,9 @@
     .schema-column-menu-button {
         cursor: pointer;
         padding: 2px 5px;
-        background: $medium-blue;
+        background: global.$medium-blue;
         border-radius: 9999px;
-        color: $white;
+        color: global.$white;
         width: fit-content;
         margin: 5px;
         align-self: center;
@@ -141,7 +141,7 @@
 }
 
 .schema-column-header {
-    background-color: $light-gray;
+    background-color: global.$light-gray;
     font-weight: bold;
     display: flex;
     flex-direction: row;
@@ -149,7 +149,7 @@
     align-items: center;
     padding: 0;
     z-index: 1;
-    border: 1px solid $medium-gray;
+    border: 1px solid global.$medium-gray;
 
     .schema-column-title {
         flex-grow: 2;
@@ -175,7 +175,7 @@
         cursor: ew-resize;
 
         &:hover {
-            background-color: $light-blue;
+            background-color: global.$light-blue;
         }
     }
 
@@ -188,7 +188,7 @@
         min-width: 5px;
         transform: translate(-3px, 0px);
         align-self: flex-start;
-        background-color: $medium-gray;
+        background-color: global.$medium-gray;
     }*/ // creates awkward thick gray borders between colheaders
 }
 
@@ -202,7 +202,7 @@
 }
 
 .schema-header-row {
-    background-color: $light-gray;
+    background-color: global.$light-gray;
     overflow: hidden;
 }
 
@@ -226,7 +226,7 @@
 
 .schema-table-cell,
 .row-menu {
-    border: 1px solid $medium-gray;
+    border: 1px solid global.$medium-gray;
     overflow-x: hidden;
     overflow-y: auto;
     display: inline-flex;
@@ -264,7 +264,7 @@
     .row-menu-infos {
         position: absolute;
         top: 3;
-        left: 3; 
+        left: 3;
         z-index: 1;
         display: flex;
         justify-content: flex-end;
@@ -278,7 +278,7 @@
 
 .schema-row-button,
 .schema-header-button {
-    color: $dark-gray;
+    color: global.$dark-gray;
     margin: 3px;
     cursor: pointer;
     display: flex;
@@ -294,7 +294,7 @@
     width: 17px;
     height: 17px;
     border-radius: 30%;
-    background-color: $dark-gray;
+    background-color: global.$dark-gray;
     color: white;
     margin: 3px;
     cursor: pointer;
@@ -311,5 +311,3 @@
     outline: none;
     height: 100%;
 }
-
-
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 636b6415c..ebf60b287 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .linkMenu {
     width: auto;
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index 66ddd6eca..3cd60c87f 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -1,7 +1,7 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .linkMenu-item {
-    // border-top: 0.5px solid $medium-gray;
+    // border-top: 0.5px solid global.$medium-gray;
     position: relative;
     display: flex;
     border-top: 0.5px solid #cdcdcd;
@@ -120,7 +120,7 @@
         border-radius: 50%;
         pointer-events: auto;
         background-color: red;
-        color: $white;
+        color: global.$white;
         font-size: 65%;
         transition: transform 0.2s;
         text-align: center;
@@ -138,7 +138,7 @@
         }
 
         &:hover {
-            background: $medium-gray;
+            background: global.$medium-gray;
             cursor: pointer;
         }
     }
diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss
index 74fbfbb2c..cb1e11780 100644
--- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss
+++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.scss
@@ -1,15 +1,15 @@
-@import '../NewLightboxStyles.scss';
+@use '../NewLightboxStyles.scss' as newstyles;
 
 .newLightboxButtonMeny-container {
     width: 100vw;
     height: 100vh;
-  
+
     &.dark {
-      background: $black;
+        background: newstyles.$black;
     }
-  
+
     &.light,
     &.default {
-      background: $white;
+        background: newstyles.$white;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.scss b/src/client/views/newlightbox/ExploreView/ExploreView.scss
index 5a8ab2f87..2c264c514 100644
--- a/src/client/views/newlightbox/ExploreView/ExploreView.scss
+++ b/src/client/views/newlightbox/ExploreView/ExploreView.scss
@@ -1,4 +1,4 @@
-@import '../NewLightboxStyles.scss';
+@use '../NewLightboxStyles.scss' as newstyles;
 
 .exploreView-container {
     width: 100%;
@@ -6,39 +6,39 @@
     border-radius: 20px;
     position: relative;
     // transform: scale(1);
-    background: $gray-l1;
-    border-top: $standard-border;
-    border-color: $gray-l2;
+    background: newstyles.$gray-l1;
+    border-top: newstyles.$standard-border;
+    border-color: newstyles.$gray-l2;
     border-radius: 0px 0px 20px 20px;
     transform-origin: 50% 50%;
     overflow: hidden;
 
     &.dark {
-      background: $black;
+        background: newstyles.$black;
     }
-  
+
     &.light,
     &.default {
-      background: $gray-l1;
+        background: newstyles.$gray-l1;
     }
 
     .exploreView-doc {
-      width: 60px;
-      height: 80px;
-      position: absolute;
-      background: $blue-l2;
-      // opacity: 0.8;
-      transform-origin: 50% 50%;
-      transform: translate(-50%, -50%) scale(1);
-      cursor: pointer;
-      transition: 0.2s ease;
-      overflow: hidden;
-      font-size: 9px;
-      padding: 10px;
-      border-radius: 5px;
+        width: 60px;
+        height: 80px;
+        position: absolute;
+        background: newstyles.$blue-l2;
+        // opacity: 0.8;
+        transform-origin: 50% 50%;
+        transform: translate(-50%, -50%) scale(1);
+        cursor: pointer;
+        transition: 0.2s ease;
+        overflow: hidden;
+        font-size: 9px;
+        padding: 10px;
+        border-radius: 5px;
 
-      &:hover {
-        transform: translate(calc(-50% * 1.125), calc(-50% * 1.125)) scale(1.5);
-      }
+        &:hover {
+            transform: translate(calc(-50% * 1.125), calc(-50% * 1.125)) scale(1.5);
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/newlightbox/Header/LightboxHeader.scss b/src/client/views/newlightbox/Header/LightboxHeader.scss
index a9e60ea98..5b316890d 100644
--- a/src/client/views/newlightbox/Header/LightboxHeader.scss
+++ b/src/client/views/newlightbox/Header/LightboxHeader.scss
@@ -1,9 +1,9 @@
-@import '../NewLightboxStyles.scss';
+@use '../NewLightboxStyles.scss' as newstyles;
 
 .newLightboxHeader-container {
     width: 100%;
     height: 100%;
-    background: $gray-l1;
+    background: newstyles.$gray-l1;
     border-radius: 20px 20px 0px 0px;
     padding: 20px;
     display: grid;
@@ -29,13 +29,13 @@
         grid-row: 2;
         .type {
             padding: 2px 7px !important;
-            background: $gray-l2;
+            background: newstyles.$gray-l2;
         }
     }
 
     .lb-label {
-        color: $gray-l3;
-        font-weight: $h1-weight;
+        color: newstyles.$gray-l3;
+        font-weight: newstyles.$h1-weight;
     }
 
     .lb-button {
@@ -47,25 +47,25 @@
         justify-content: space-evenly;
         align-items: center;
         transition: 0.2s ease;
-        gap: 5px; 
-        font-size: $body-size;
+        gap: 5px;
+        font-size: newstyles.$body-size;
         height: fit-content;
 
         &:hover {
-            background: $gray-l2;
+            background: newstyles.$gray-l2;
         }
 
         &.true {
-            background: $blue-l1;
+            background: newstyles.$blue-l1;
         }
     }
-    
+
     &.dark {
-      background: $black;
+        background: newstyles.$black;
     }
-  
+
     &.light,
     &.default {
-      background: $white;
+        background: newstyles.$white;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.scss b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
index 40dd47e47..99c935e0c 100644
--- a/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
@@ -1,4 +1,4 @@
-@import '../NewLightboxStyles.scss';
+@use '../NewLightboxStyles.scss' as newstyles;
 
 .recommendationlist-container {
     height: calc(100% - 40px);
@@ -7,111 +7,110 @@
     overflow-y: scroll;
 
     .recommendations {
-      height: fit-content;
-      padding: 20px;
-      display: flex;
-      flex-direction: column;
-      gap: 20px;
-      background: $gray-l1;
-      border-radius: 0px 0px 20px 20px;
+        height: fit-content;
+        padding: 20px;
+        display: flex;
+        flex-direction: column;
+        gap: 20px;
+        background: newstyles.$gray-l1;
+        border-radius: 0px 0px 20px 20px;
     }
 
     .header {
-      top: 0px;
-      position: sticky;
-      background: $gray-l1;
-      border-bottom: $standard-border;
-      border-color: $gray-l2;
-      display: flex;
-      flex-direction: column;
-      justify-content: flex-start;
-      align-items: flex-start;
-      border-radius: 20px 20px 0px 0px;
-      padding: 20px;
-      z-index: 2;
-      gap: 10px;
-      color: $text-color-lm;
-
-      .lb-label {
-        color: $gray-l3;
-        font-weight: $h1-weight;
-        font-size: $body-size;
-      }
-
-      .lb-caret {
+        top: 0px;
+        position: sticky;
+        background: newstyles.$gray-l1;
+        border-bottom: newstyles.$standard-border;
+        border-color: newstyles.$gray-l2;
         display: flex;
-        flex-direction: row;
-        justify-content: flex-end;
-        align-items: center;
-        gap: 5px;
-        cursor: pointer;
-        width: 100%;
-        user-select: none;
-        font-size: $body-size;
-      }
+        flex-direction: column;
+        justify-content: flex-start;
+        align-items: flex-start;
+        border-radius: 20px 20px 0px 0px;
+        padding: 20px;
+        z-index: 2;
+        gap: 10px;
+        color: newstyles.$text-color-lm;
 
-      .more {
-        width: 100%;
-      }
+        .lb-label {
+            color: newstyles.$gray-l3;
+            font-weight: newstyles.$h1-weight;
+            font-size: newstyles.$body-size;
+        }
 
-      &.dark {
-        color: $text-color-dm;
-      }
+        .lb-caret {
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-end;
+            align-items: center;
+            gap: 5px;
+            cursor: pointer;
+            width: 100%;
+            user-select: none;
+            font-size: newstyles.$body-size;
+        }
 
-      .title {
-        height: 30px;
-        min-height: 30px;
-        font-size: $h1-size;
-        font-weight: $h1-weight;
-        text-align: left;
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        width: 100%;
-      }
+        .more {
+            width: 100%;
+        }
 
-      .keywords {
-        display: flex;
-        flex-flow: row wrap;
-        gap: 5px;
+        &.dark {
+            color: newstyles.$text-color-dm;
+        }
 
-        .keyword-input {
-          padding: 3px 7px;
-          background: $gray-l2;
-          outline: none;
-          border: none;
-          height: 21.5px;
-          color: $text-color-lm;
+        .title {
+            height: 30px;
+            min-height: 30px;
+            font-size: newstyles.$h1-size;
+            font-weight: newstyles.$h1-weight;
+            text-align: left;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            width: 100%;
         }
 
-        .keyword {
-          padding: 3px 7px;
-          width: fit-content;
-          background: $gray-l2;
-          display: flex;
-          justify-content: center;
-          align-items: center;
-          flex-direction: row;
-          gap: 10px;
-          font-size: $body-size;
-          font-weight: $body-weight;
+        .keywords {
+            display: flex;
+            flex-flow: row wrap;
+            gap: 5px;
 
-          &.loading {
-            animation: skeleton-loading-l2 1s linear infinite alternate;
-            min-width: 70px;
-            height: 21.5px;
-          }
-        }
+            .keyword-input {
+                padding: 3px 7px;
+                background: newstyles.$gray-l2;
+                outline: none;
+                border: none;
+                height: 21.5px;
+                color: newstyles.$text-color-lm;
+            }
+
+            .keyword {
+                padding: 3px 7px;
+                width: fit-content;
+                background: newstyles.$gray-l2;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                flex-direction: row;
+                gap: 10px;
+                font-size: newstyles.$body-size;
+                font-weight: newstyles.$body-weight;
 
-      }
+                &.loading {
+                    animation: skeleton-loading-l2 1s linear infinite alternate;
+                    min-width: 70px;
+                    height: 21.5px;
+                }
+            }
+        }
     }
-  
+
     &.dark {
-      background: $black;
+        background: newstyles.$black;
     }
-  
+
     &.light,
     &.default {
-      background: $gray-l1;
+        background: newstyles.$gray-l1;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/newlightbox/components/EditableText/EditableText.scss b/src/client/views/newlightbox/components/EditableText/EditableText.scss
index 7828538ab..8007e8d43 100644
--- a/src/client/views/newlightbox/components/EditableText/EditableText.scss
+++ b/src/client/views/newlightbox/components/EditableText/EditableText.scss
@@ -1,34 +1,34 @@
-@import '../../NewLightboxStyles.scss';
+@use '../../NewLightboxStyles.scss' as newstyles;
 
 .lb-editableText,
 .lb-displayText {
     padding: 4px 7px !important;
-    border: $standard-border !important;
-    border-color: $gray-l2 !important;
+    border: newstyles.$standard-border !important;
+    border-color: newstyles.$gray-l2 !important;
 }
 
 .lb-editableText {
-  -webkit-appearance: none;
-  overflow: hidden;
-  font-size: inherit;
-  border: none;
-  outline: none;
-  width: 100%;
-  margin: 0px;
-  padding: 0px;
-  box-shadow: none !important;
-  background: none;
-
-  &:focus {
+    -webkit-appearance: none;
+    overflow: hidden;
+    font-size: inherit;
+    border: none;
     outline: none;
-    background-color: $blue-l1;
-  }
+    width: 100%;
+    margin: 0px;
+    padding: 0px;
+    box-shadow: none !important;
+    background: none;
+
+    &:focus {
+        outline: none;
+        background-color: newstyles.$blue-l1;
+    }
 }
 
 .lb-displayText {
-  cursor: text !important;
-  width: 100%;
-  display: flex;
-  align-items: center;
-  font-size: inherit;
-}
\ No newline at end of file
+    cursor: text !important;
+    width: 100%;
+    display: flex;
+    align-items: center;
+    font-size: inherit;
+}
diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.scss b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
index c86c63ba0..cf6b5ccb1 100644
--- a/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
+++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.scss
@@ -1,4 +1,4 @@
-@import '../../NewLightboxStyles.scss';
+@use '../../NewLightboxStyles.scss' as newstyles;
 
 .recommendation-container {
     width: 100%;
@@ -8,22 +8,22 @@
     display: grid;
     grid-template-columns: 0% 100%;
     grid-template-rows: auto auto auto auto auto;
-    gap: 5px 0px;   
+    gap: 5px 0px;
     padding: 10px;
     cursor: pointer;
     transition: 0.2s ease;
-    border: $standard-border;
-    border-color: $gray-l2;
+    border: newstyles.$standard-border;
+    border-color: newstyles.$gray-l2;
     background: white;
 
     &:hover {
-    //   background: white !important;
-      transform: scale(1.02);
-      z-index: 0;
+        //   background: white !important;
+        transform: scale(1.02);
+        z-index: 0;
 
-      .title {
-        text-decoration: underline;
-      }
+        .title {
+            text-decoration: underline;
+        }
     }
 
     &.previewUrl {
@@ -39,18 +39,18 @@
         grid-template-rows: auto auto auto auto auto;
         gap: 5px 10px;
 
-      .image-container,
-      .title,
-      .info,
-      .source,
-      .explainer,
-      .hide-rec {
-        animation: skeleton-loading-l3 1s linear infinite alternate;
-      }
+        .image-container,
+        .title,
+        .info,
+        .source,
+        .explainer,
+        .hide-rec {
+            animation: skeleton-loading-l3 1s linear infinite alternate;
+        }
 
-      .title {
-        border-radius: 20px;
-      }
+        .title {
+            border-radius: 20px;
+        }
     }
 
     .distance-container,
@@ -64,62 +64,62 @@
     }
 
     .image-container {
-      grid-row: 2/5;
-      grid-column: 1;
-      border-radius: 20px;
-      overflow: hidden;
+        grid-row: 2/5;
+        grid-column: 1;
+        border-radius: 20px;
+        overflow: hidden;
 
-      .image {
-        width: 100%;
-        height: 100%;
-        object-fit: cover;
-      }
+        .image {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+        }
     }
 
     .title {
-      grid-row: 1;
-      grid-column: 1/3;
-      border-radius: 20px;
-      font-size: $h2-size;
-      font-weight: $h2-weight;
-      overflow: hidden;
-      border-radius: 0px;
-      min-height: 30px;
+        grid-row: 1;
+        grid-column: 1/3;
+        border-radius: 20px;
+        font-size: newstyles.$h2-size;
+        font-weight: newstyles.$h2-weight;
+        overflow: hidden;
+        border-radius: 0px;
+        min-height: 30px;
     }
 
     .info {
-      grid-row: 2;
-      grid-column: 2;
-      border-radius: 20px;
-      display: flex;
-      flex-direction: row;
-      gap: 5px;
-      font-size: $body-size;
+        grid-row: 2;
+        grid-column: 2;
+        border-radius: 20px;
+        display: flex;
+        flex-direction: row;
+        gap: 5px;
+        font-size: newstyles.$body-size;
 
         .lb-type {
             padding: 2px 7px !important;
-            background: $gray-l2;
+            background: newstyles.$gray-l2;
         }
     }
 
     .lb-label {
-        color: $gray-l3;
-        font-weight: $h1-weight;
-        font-size: $body-size;
+        color: newstyles.$gray-l3;
+        font-weight: newstyles.$h1-weight;
+        font-size: newstyles.$body-size;
     }
 
     .source {
         grid-row: 3;
         grid-column: 2;
         border-radius: 20px;
-        font-size: $body-size;
+        font-size: newstyles.$body-size;
         display: flex;
         justify-content: flex-start;
         align-items: center;
 
         .lb-source {
             padding: 2px 7px !important;
-            background: $gray-l2;
+            background: newstyles.$gray-l2;
             border-radius: 10px;
             white-space: nowrap;
             max-width: 130px;
@@ -134,7 +134,7 @@
         border-radius: 20px;
         font-size: 10px;
         width: 100%;
-        background: $blue-l1;
+        background: newstyles.$blue-l1;
         border-radius: 0;
         padding: 10px;
 
@@ -145,7 +145,7 @@
             gap: 3px;
             .concept {
                 padding: 2px 7px !important;
-                background: $gray-l2;
+                background: newstyles.$gray-l2;
             }
         }
     }
@@ -154,7 +154,7 @@
         grid-row: 5;
         grid-column: 2;
         border-radius: 20px;
-        font-size: $body-size;
+        font-size: newstyles.$body-size;
         display: flex;
         align-items: center;
         margin-top: 5px;
@@ -162,15 +162,15 @@
         justify-content: flex-end;
         text-transform: underline;
     }
-  
+
     &.dark {
-      background: $black;
-      border-color: $white;
+        background: newstyles.$black;
+        border-color: newstyles.$white;
     }
-  
+
     &.light,
     &.default {
-      background: $white;
-      border-color: $white;
+        background: newstyles.$white;
+        border-color: newstyles.$white;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss
index e541e3f3c..bbc730144 100644
--- a/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss
+++ b/src/client/views/newlightbox/components/SkeletonDoc/SkeletonDoc.scss
@@ -1,82 +1,81 @@
-@import '../../NewLightboxStyles.scss';
+@use '../../NewLightboxStyles.scss' as newstyles;
 
 .skeletonDoc-container {
-  display: flex;
-  flex-direction: column;
-  height: calc(100% - 40px);
-  margin: 20px;
-  gap: 20px;
+    display: flex;
+    flex-direction: column;
+    height: calc(100% - 40px);
+    margin: 20px;
+    gap: 20px;
 
-  .header {
-    width: calc(100% - 20px);
-    height: 80px;
-    background: $gray-l2;
-    animation: skeleton-loading-l2 1s linear infinite alternate; 
-    display: grid;
-    grid-template-rows: 60% 40%;
-    padding: 10px;
-    grid-template-columns: auto auto auto auto;
-    border-radius: 20px;
+    .header {
+        width: calc(100% - 20px);
+        height: 80px;
+        background: newstyles.$gray-l2;
+        animation: skeleton-loading-l2 1s linear infinite alternate;
+        display: grid;
+        grid-template-rows: 60% 40%;
+        padding: 10px;
+        grid-template-columns: auto auto auto auto;
+        border-radius: 20px;
 
-    .title {
-      grid-row: 1;
-      grid-column: 1 / 5;
-      display: flex;
-      width: fit-content;
-      height: 100%;
-      min-width: 500px;
-      font-size: $title-size;
-      animation: skeleton-loading-l3 1s linear infinite alternate;
-      border-radius: 20px;
-    }
+        .title {
+            grid-row: 1;
+            grid-column: 1 / 5;
+            display: flex;
+            width: fit-content;
+            height: 100%;
+            min-width: 500px;
+            font-size: newstyles.$title-size;
+            animation: skeleton-loading-l3 1s linear infinite alternate;
+            border-radius: 20px;
+        }
 
-    .type {
-      display: flex;
-      padding: 3px 7px;
-      width: fit-content;
-      height: fit-content;
-      margin-top: 8px;
-      min-height: 15px;
-      min-width: 60px;
-      grid-row: 2;
-      grid-column: 1;
-      animation: skeleton-loading-l3 1s linear infinite alternate;
-      border-radius: 20px;
-    }
+        .type {
+            display: flex;
+            padding: 3px 7px;
+            width: fit-content;
+            height: fit-content;
+            margin-top: 8px;
+            min-height: 15px;
+            min-width: 60px;
+            grid-row: 2;
+            grid-column: 1;
+            animation: skeleton-loading-l3 1s linear infinite alternate;
+            border-radius: 20px;
+        }
 
-    .buttons-container {
-      grid-row: 1 / 3;
-      grid-column: 5;
-      display: flex;
-      justify-content: flex-end;
-      align-items: center;
-      gap: 10px;
+        .buttons-container {
+            grid-row: 1 / 3;
+            grid-column: 5;
+            display: flex;
+            justify-content: flex-end;
+            align-items: center;
+            gap: 10px;
 
-      .button {
-        width: 50px;
-        height: 50px;
-        border-radius: 100%;
-        animation: skeleton-loading-l3 1s linear infinite alternate;
-      }
+            .button {
+                width: 50px;
+                height: 50px;
+                border-radius: 100%;
+                animation: skeleton-loading-l3 1s linear infinite alternate;
+            }
+        }
     }
 
-  }
-
-  .content {
-    width: 100%;
-    flex: 1;
-    -webkit-flex: 1; /* Chrome */
-    background: $gray-l2;
-    animation: skeleton-loading-l2 1s linear infinite alternate;
-    border-radius: 20px;
-  }
+    .content {
+        width: 100%;
+        flex: 1;
+        -webkit-flex: 1; /* Chrome */
+        background: newstyles.$gray-l2;
+        animation: skeleton-loading-l2 1s linear infinite alternate;
+        border-radius: 20px;
+    }
 
     // &.dark {
-    //   background: $black;
+    //   background: newstyles.$black;
     // }
-  
+
     // &.light,
     // &.default {
-    //   background: $white;
+    //   background: newstyles.$white;
     // }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/newlightbox/components/Template/Template.scss b/src/client/views/newlightbox/components/Template/Template.scss
index 5b72ddaf9..c2fb9fba4 100644
--- a/src/client/views/newlightbox/components/Template/Template.scss
+++ b/src/client/views/newlightbox/components/Template/Template.scss
@@ -1,15 +1,15 @@
-@import '../../NewLightboxStyles.scss';
+@use '../../NewLightboxStyles.scss' as newstyles;
 
 .template-container {
     width: 100vw;
     height: 100vh;
-  
+
     &.dark {
-      background: $black;
+        background: newstyles.$black;
     }
-  
+
     &.light,
     &.default {
-      background: $white;
+        background: newstyles.$white;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 4337401e3..933a383ea 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .audiobox-container {
     width: 100%;
@@ -19,30 +19,30 @@
 
     .audiobox-dictation {
         width: 40px;
-        background: $medium-gray;
-        color: $dark-gray;
+        background: global.$medium-gray;
+        color: global.$dark-gray;
         display: flex;
         justify-content: center;
         align-items: center;
 
         &:hover {
-            color: $black;
+            color: global.$black;
         }
     }
 
     .audiobox-start-record {
-        color: $white;
-        background: $dark-gray;
+        color: global.$white;
+        background: global.$dark-gray;
         display: flex;
         align-items: center;
         justify-content: center;
-        font-size: $body-text;
+        font-size: global.$body-text;
         width: 100%;
         height: 100%;
         gap: 5px;
 
         &:hover {
-            background: $black;
+            background: global.$black;
         }
     }
 
@@ -54,11 +54,11 @@
         gap: 5px;
         width: 100%;
         height: 100%;
-        background: $dark-gray;
+        background: global.$dark-gray;
         color: white;
 
         .record-timecode {
-            font-size: $large-header;
+            font-size: global.$large-header;
         }
 
         .record-button {
@@ -66,7 +66,7 @@
             width: 30px;
             height: 30px;
             border-radius: 50%;
-            background: $dark-gray;
+            background: global.$dark-gray;
             display: flex;
             align-items: center;
             justify-content: center;
@@ -76,7 +76,7 @@
             }
 
             &:hover {
-                background: $black;
+                background: global.$black;
             }
         }
     }
@@ -87,10 +87,10 @@
     display: flex;
     flex-direction: column;
     align-items: center;
-    background: $dark-gray;
+    background: global.$dark-gray;
     width: 100%;
     height: 100%;
-    color: $white;
+    color: global.$white;
 
     .audiobox-button {
         margin: 2.5px;
@@ -98,7 +98,7 @@
         width: 25px;
         height: 25px;
         border-radius: 50%;
-        background: $dark-gray;
+        background: global.$dark-gray;
         display: flex;
         align-items: center;
         justify-content: center;
@@ -108,7 +108,7 @@
         }
 
         &:hover {
-            background: $black;
+            background: global.$black;
         }
     }
 
@@ -132,7 +132,7 @@
         height: 6px;
         cursor: pointer;
         box-shadow: 0;
-        background: $light-gray;
+        background: global.$light-gray;
         border-radius: 3px;
     }
 
@@ -142,7 +142,7 @@
         height: 10px;
         width: 10px;
         border-radius: 10px;
-        background: $medium-blue;
+        background: global.$medium-blue;
         cursor: pointer;
         -webkit-appearance: none;
         margin-top: -2px;
@@ -180,12 +180,12 @@
     .audiobox-playback {
         width: 100%;
         height: 100%;
-        background: $white;
+        background: global.$white;
 
         .audiobox-timeline {
             height: calc(100% - 50px);
             width: 100%;
-            background: $white;
+            background: global.$white;
             position: absolute;
         }
 
@@ -203,7 +203,7 @@
         width: 100%;
         height: 20px;
         padding: 3px;
-        font-size: $small-text;
+        font-size: global.$small-text;
 
         .bottom-controls-middle {
             display: flex;
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 0eb27b65b..ff1fa343d 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -1,4 +1,4 @@
-@import '../../../global/globalCssVariables.module.scss';
+@use '../../../global/globalCssVariables.module.scss' as global;
 .chart-container {
     display: flex;
     flex-direction: column;
@@ -108,7 +108,7 @@
             }
         }
         tr td {
-            height: $DATA_VIZ_TABLE_ROW_HEIGHT !important; // bcz: hack.  you can't set a <tr> height directly, but you can set the height of all of it's <td>s.  So this is the height of a tableBox row.
+            height: global.$DATA_VIZ_TABLE_ROW_HEIGHT !important; // bcz: hack.  you can't set a <tr> height directly, but you can set the height of all of it's <td>s.  So this is the height of a tableBox row.
             padding: 0 !important;
             vertical-align: middle !important;
         }
@@ -135,7 +135,7 @@
 }
 
 .tableBox-filterPopup {
-    background: $light-gray;
+    background: global.$light-gray;
     position: absolute;
     min-width: 235px;
     top: 60px;
@@ -152,7 +152,7 @@
         .tableBox-filterPopup-selectColumn-each {
             margin-left: 25px;
             border-radius: 3px;
-            background: $light-gray;
+            background: global.$light-gray;
         }
     }
     .tableBox-filterPopup-setValue {
@@ -162,7 +162,7 @@
         .tableBox-filterPopup-setValue-each {
             margin-right: 5px;
             border-radius: 3px;
-            background: $light-gray;
+            background: global.$light-gray;
         }
         .tableBox-filterPopup-setValue-input {
             margin: 5px;
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index b32b27e65..e1b83dc59 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .documentLinksButton-wrapper {
     transform-origin: top left;
@@ -29,7 +29,7 @@
     pointer-events: auto;
     display: flex;
     align-items: center;
-    background-color: $light-blue;
+    background-color: global.$light-blue;
     color: black;
 }
 .documentLinksButton,
@@ -59,30 +59,30 @@
     }
 }
 .documentLinksButton {
-    background-color: $dark-gray;
-    color: $white;
+    background-color: global.$dark-gray;
+    color: global.$white;
     font-weight: bold;
     font-size: 100%;
     font-family: 'Roboto';
     transition: 0.2s ease all;
 
     &:hover {
-        background-color: $black;
+        background-color: global.$black;
     }
 }
 .documentLinksButton.startLink {
-    background-color: $medium-blue;
+    background-color: global.$medium-blue;
     width: 75%;
     height: 75%;
-    color: $white;
+    color: global.$white;
     font-weight: bold;
     font-size: 100%;
     transition: 0.2s ease all;
 }
 
 .documentLinksButton-endLink {
-    border: $medium-blue 2px dashed;
-    color: $medium-blue;
+    border: global.$medium-blue 2px dashed;
+    color: global.$medium-blue;
     background-color: none !important;
     font-size: 100%;
     transition: 0.2s ease all;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 7e5507586..294af4d96 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .documentView-effectsWrapper {
     border-radius: inherit;
@@ -28,7 +28,7 @@
     // overflow: hidden; // need this so that title will be clipped when borderRadius is set
     //  transition: outline 0.3s linear;
 
-    // background: $white; //overflow: hidden;
+    // background: global.$white; //overflow: hidden;
     transform-origin: center;
 
     &.minimized {
@@ -180,7 +180,7 @@
 
         .documentView-titleWrapper,
         .documentView-titleWrapper-hover {
-            color: $black;
+            color: global.$black;
             transform-origin: top left;
             top: 0;
             width: 100%;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 441d6053f..b40ead03a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -778,7 +778,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                         style={{
                             width: `${100 / this.uiBtnScaling}%`, //
                             transform: `scale(${this.uiBtnScaling})`,
-                            bottom: this.maxWidgetSize,
+                            bottom: Number.isNaN(this.maxWidgetSize) ? undefined : this.maxWidgetSize,
                         }}>
                         {this._props.DocumentView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
                     </div>
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index 55e0f5184..bcbb44e68 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -1,5 +1,3 @@
-@import '../global/globalCssVariables.module.scss';
-
 .equationBox-cont {
     transform-origin: center;
     width: fit-content;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index 2405889cf..186d24e92 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.module.scss';
+@use '../../global/globalCssVariables.module.scss' as global;
 
 // bcz: something's messed up with the IconButton css.  this mostly fixes the fit-all button, the color buttons, the undo +/- expander and the dropdown doc type list (eg 'text')
 .iconButton-container {
@@ -23,7 +23,7 @@
     justify-content: center;
     align-items: center;
     font-size: 80%;
-    border-radius: $standard-border-radius;
+    border-radius: global.$standard-border-radius;
     transition: 0.15s;
 
     .menuButton-wrap {
@@ -34,7 +34,7 @@
     }
 
     .fontIconBox-label {
-        color: $white;
+        color: global.$white;
         bottom: -1;
         position: absolute;
         text-align: center;
@@ -124,17 +124,17 @@
                 width: 21px;
                 left: 2px;
                 bottom: 2px;
-                background-color: $white;
+                background-color: global.$white;
                 -webkit-transition: 0.4s;
                 transition: 0.4s;
             }
 
             input:checked + .slider {
-                background-color: $medium-blue;
+                background-color: global.$medium-blue;
             }
 
             input:focus + .slider {
-                box-shadow: 0 0 1px $medium-blue;
+                box-shadow: 0 0 1px global.$medium-blue;
             }
 
             input:checked + .slider:before {
@@ -145,11 +145,11 @@
 
             /* Rounded sliders */
             .slider.round {
-                border-radius: $standard-border-radius;
+                border-radius: global.$standard-border-radius;
             }
 
             .slider.round:before {
-                border-radius: $standard-border-radius;
+                border-radius: global.$standard-border-radius;
             }
         }
 
@@ -259,12 +259,12 @@
             height: fit-content;
             top: 100%;
             z-index: 21;
-            background-color: $white;
+            background-color: global.$white;
             box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
             padding: 1px;
 
             .list-item {
-                color: $black;
+                color: global.$black;
                 width: 100%;
                 height: 25px;
                 font-weight: 400;
@@ -285,7 +285,7 @@
         background: transparent;
 
         &.slider {
-            color: $white;
+            color: global.$white;
             cursor: pointer;
             flex-direction: column;
             background: transparent;
@@ -302,7 +302,7 @@
                 z-index: 21;
                 background-color: #e3e3e3;
                 box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
-                border-radius: $standard-border-radius;
+                border-radius: global.$standard-border-radius;
 
                 .menu-slider {
                     height: 10px;
@@ -340,7 +340,7 @@
                     border: none;
                     text-align: right;
                     width: 100%;
-                    color: $white;
+                    color: global.$white;
                     height: 100%;
                     text-align: center;
                 }
@@ -354,7 +354,7 @@
         &.list {
             width: 100%;
             justify-content: space-around;
-            border: $standard-border;
+            border: global.$standard-border;
 
             .menuButton-dropdownList {
                 position: absolute;
@@ -365,12 +365,12 @@
                 overflow-y: scroll;
                 top: 100%;
                 z-index: 21;
-                background-color: $white;
+                background-color: global.$white;
                 box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
                 padding: 1px;
 
                 .list-item {
-                    color: $black;
+                    color: global.$black;
                     width: 100%;
                     height: 25px;
                     font-weight: 400;
@@ -394,7 +394,7 @@
         padding-left: 10px;
         justify-content: flex-start;
         color: black;
-        background-color: $light-gray;
+        background-color: global.$light-gray;
         padding: 5px;
         padding-left: 10px;
         width: 100%;
@@ -417,7 +417,7 @@
             top: 100%;
             background-color: #e3e3e3;
             box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
-            border-radius: $standard-border-radius;
+            border-radius: global.$standard-border-radius;
         }
     }
 
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index c79d662f4..202b0c701 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .card-button-container {
     display: flex;
@@ -18,7 +18,7 @@
         margin: auto;
         padding: 0;
         border-radius: 50%;
-        background-color: $dark-gray;
+        background-color: global.$dark-gray;
         background-color: transparent;
     }
 }
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index a44f614b2..441fceba4 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -1,11 +1,11 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 .keyValueBox-cont {
     overflow-y: scroll;
     width: 100%;
     height: 100%;
-    background-color: $white;
-    border: 1px solid $medium-gray;
-    border-radius: $border-radius;
+    background-color: global.$white;
+    border: 1px solid global.$medium-gray;
+    border-radius: global.$border-radius;
     box-sizing: border-box;
     display: inline-block;
     cursor: default;
@@ -56,8 +56,8 @@ $header-height: 30px;
     width: 100%;
     position: relative;
     display: inline-block;
-    background: $medium-gray;
-    color: $white;
+    background: global.$medium-gray;
+    color: global.$white;
     text-transform: uppercase;
     letter-spacing: 2px;
     font-size: 12px;
@@ -66,7 +66,7 @@ $header-height: 30px;
     th {
         font-weight: normal;
         &:first-child {
-            border-right: 1px solid $white;
+            border-right: 1px solid global.$white;
         }
     }
 }
@@ -76,9 +76,9 @@ $header-height: 30px;
     display: flex;
     width: 100%;
     height: $header-height;
-    background: $white;
+    background: global.$white;
     .formattedTextBox-cont {
-        background: $white;
+        background: global.$white;
     }
 }
 .keyValueBox-cont {
@@ -116,8 +116,8 @@ $header-height: 30px;
     display: flex;
     width: 100%;
     height: 30px;
-    background: $light-gray;
+    background: global.$light-gray;
     .formattedTextBox-cont {
-        background: $light-gray;
+        background: global.$light-gray;
     }
 }
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 46ea9c18e..913ab641c 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .keyValuePair-td-key {
     display: inline-block;
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
index 104301656..b44b69af5 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.scss
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -1,12 +1,12 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .linkDescriptionPopup {
     display: flex;
     flex-direction: row;
     justify-content: center;
     align-items: center;
-    border: 2px solid $medium-blue;
-    background-color: $white;
+    border: 2px solid global.$medium-blue;
+    background-color: global.$white;
     width: auto;
     position: absolute;
 
@@ -35,7 +35,7 @@
             white-space: nowrap;
             padding: 5px;
             vertical-align: middle;
-            background-color: $close-red;
+            background-color: global.$close-red;
             border-radius: 3px;
             color: black;
         }
@@ -46,7 +46,7 @@
             white-space: nowrap;
             padding: 5px;
             vertical-align: middle;
-            background-color: $light-blue;
+            background-color: global.$light-blue;
             border-radius: 3px;
             color: black;
         }
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index f4bae66bb..3eac50f1f 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -65,7 +65,7 @@ export class AnimationUtility {
         const coords: mapboxgl.LngLatLike = [this.previousLngLat.lng, this.previousLngLat.lat];
         // console.log('MAP REF: ', this.MAP_REF)
         // console.log("current elevation: ", this.MAP_REF?.queryTerrainElevation(coords));
-        let altitude = this.MAP_REF ? this.MAP_REF.queryTerrainElevation(coords) ?? 0 : 0;
+        let altitude = this.MAP_REF ? (this.MAP_REF.queryTerrainElevation(coords) ?? 0) : 0;
         if (altitude === 0) {
             altitude += 50;
         }
@@ -178,7 +178,7 @@ export class AnimationUtility {
 
         this.ROUTE_COORDINATES = routeCoordinates;
         this.PATH = turf.lineString(routeCoordinates);
-        this.PATH_DISTANCE = turf.lineDistance(this.PATH);
+        this.PATH_DISTANCE = turf.length(this.PATH);
         this.terrainDisplayed = terrainDisplayed;
 
         const bearing = this.calculateBearing(
diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss
index 25b4587a5..fdd8a29d7 100644
--- a/src/client/views/nodes/MapBox/MapBox.scss
+++ b/src/client/views/nodes/MapBox/MapBox.scss
@@ -1,4 +1,6 @@
-@import '../../global/globalCssVariables.module.scss';
+@use 'sass:color';
+@use '../../global/globalCssVariables.module.scss' as global;
+
 .mapBox {
     width: 100%;
     height: 100%;
@@ -25,14 +27,6 @@
         gap: 5px;
         align-items: center;
         width: calc(100% - 40px);
-
-        // .editableText-container {
-        //     width: 100%;
-        //     font-size: 16px !important;
-        // }
-        // input {
-        //     width: 100%;
-        // }
     }
 
     .mapbox-settings-panel {
@@ -83,7 +77,7 @@
             width: 100%;
             padding: 10px;
             &:hover {
-                background-color: lighten(rgb(187, 187, 187), 10%);
+                background-color: color.adjust(rgb(187, 187, 187), $lightness: 10%);
             }
         }
     }
@@ -167,7 +161,7 @@
         pointer-events: all;
         z-index: 1; // so it appears on top of the document's title, if shown
 
-        box-shadow: $standard-box-shadow;
+        box-shadow: global.$standard-box-shadow;
         transition: 0.2s;
 
         &:hover {
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index f6908d5fd..f2160feb7 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .pdfBox,
 .pdfBox-interactive {
@@ -22,11 +22,11 @@
 
         // glr: This should really be the same component as text and PDFs
         .pdfBox-sidebarBtn {
-            background: $black;
+            background: global.$black;
             height: 25px;
             width: 25px;
             right: 5px;
-            color: $white;
+            color: global.$white;
             display: flex;
             position: absolute;
             align-items: center;
@@ -35,7 +35,7 @@
             pointer-events: all;
             z-index: 1; // so it appears on top of the document's title, if shown
 
-            box-shadow: $standard-box-shadow;
+            box-shadow: global.$standard-box-shadow;
             transition: 0.2s;
 
             &:hover {
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 460155446..b5405f0fb 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .mini-viewer {
     cursor: grab;
@@ -22,7 +22,7 @@
         height: 100%;
         border-radius: inherit;
         opacity: 0.99; // hack!  overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
-        background: $dark-gray;
+        background: global.$dark-gray;
     }
 
     .inkingCanvas-paths-markers {
@@ -93,7 +93,7 @@
     align-items: center;
     justify-content: center;
     display: flex;
-    background-color: $dark-gray;
+    background-color: global.$dark-gray;
     color: white;
     border-radius: 100px;
     height: 40px;
@@ -128,13 +128,13 @@
         width: 25px;
         height: 25px;
         border-radius: 50%;
-        background: $dark-gray;
+        background: global.$dark-gray;
         display: flex;
         align-items: center;
         justify-content: center;
 
         &:hover {
-            background: $black;
+            background: global.$black;
         }
 
         svg {
@@ -157,7 +157,7 @@
     cursor: pointer;
 
     &:hover {
-        background-color: $medium-gray;
+        background-color: global.$medium-gray;
     }
 }
 
@@ -198,7 +198,7 @@ input[type='range']::-webkit-slider-runnable-track {
     height: 10px;
     cursor: pointer;
     box-shadow: 0;
-    background: $light-gray;
+    background: global.$light-gray;
     border-radius: 10px;
 }
 
@@ -208,7 +208,7 @@ input[type='range']::-webkit-slider-thumb {
     height: 12px;
     width: 12px;
     border-radius: 10px;
-    background: $medium-blue;
+    background: global.$medium-blue;
     cursor: pointer;
     -webkit-appearance: none;
     margin-top: -1px;
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index a1686adaf..05d5babf9 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .webBox {
     height: 100%;
@@ -120,7 +120,7 @@
         pointer-events: all;
         z-index: 1; // so it appears on top of the document's title, if shown
 
-        box-shadow: $standard-box-shadow;
+        box-shadow: global.$standard-box-shadow;
         transition: 0.2s;
 
         &:hover {
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
index 9cf760a12..3d27fa887 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss
@@ -1,3 +1,4 @@
+@use 'sass:color';
 @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
 
 $primary-color: #3f51b5;
@@ -68,7 +69,7 @@ $transition: all 0.2s ease-in-out;
             &:focus {
                 outline: none;
                 border-color: $primary-color;
-                box-shadow: 0 0 0 2px rgba($primary-color, 0.2);
+                box-shadow: 0 0 0 2px color.adjust($primary-color, $alpha: -0.8);
             }
 
             &:disabled {
@@ -92,11 +93,11 @@ $transition: all 0.2s ease-in-out;
             transition: $transition;
 
             &:hover {
-                background-color: darken($primary-color, 10%);
+                background-color: color.adjust($primary-color, $lightness: -10%);
             }
 
             &:disabled {
-                background-color: lighten($primary-color, 20%);
+                background-color: color.adjust($primary-color, $lightness: 20%);
                 cursor: not-allowed;
             }
 
@@ -178,7 +179,7 @@ $transition: all 0.2s ease-in-out;
         margin-bottom: 16px;
 
         &:hover {
-            background-color: rgba($primary-color, 0.1);
+            background-color: color.adjust($primary-color, $alpha: -0.9);
         }
     }
 
@@ -220,7 +221,7 @@ $transition: all 0.2s ease-in-out;
             transition: $transition;
 
             &:hover {
-                background-color: rgba($primary-color, 0.2);
+                background-color: color.adjust($primary-color, $alpha: -0.8);
                 color: #fff;
             }
         }
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index d79df4272..78bbb520e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.module.scss';
+@use '../../global/globalCssVariables.module.scss' as global;
 
 .dashFieldView-active,
 .dashFieldView {
@@ -64,5 +64,5 @@
 }
 
 .ProseMirror-selectedNode {
-    outline: solid 1px $light-blue !important;
+    outline: solid 1px global.$light-blue !important;
 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 84859b94d..f9de4ab5a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.module.scss';
+@use '../../global/globalCssVariables.module.scss' as global;
 
 .ProseMirror {
     width: 100%;
@@ -22,7 +22,7 @@
 
     &.h-left * {
         display: flex;
-        justify-content: flex-start; 
+        justify-content: flex-start;
     }
 
     &.h-right * {
@@ -32,7 +32,7 @@
 
     &.template * {
         ::-webkit-scrollbar-track {
-            background: none; 
+            background: none;
         }
     }
 
@@ -64,7 +64,7 @@ audiotag:hover {
     background: inherit;
     padding: 0;
     border-width: 0px;
-    border-color: $medium-gray;
+    border-color: global.$medium-gray;
     box-sizing: border-box;
     background-color: inherit;
     border-style: solid;
@@ -79,7 +79,6 @@ audiotag:hover {
     transform-origin: left top;
     top: 0;
     left: 0;
-    
 }
 
 .formattedTextBox-cont {
@@ -88,7 +87,7 @@ audiotag:hover {
     padding: 0;
     border-width: 0px;
     border-radius: inherit;
-    border-color: $medium-gray;
+    border-color: global.$medium-gray;
     box-sizing: border-box;
     background-color: inherit;
     border-style: solid;
@@ -147,13 +146,13 @@ audiotag:hover {
     font-size: 11px;
     border-radius: 3px;
     color: white;
-    background: $medium-gray;
+    background: global.$medium-gray;
     border-radius: 5px;
     display: flex;
     justify-content: center;
     align-items: center;
     cursor: grabbing;
-    box-shadow: $standard-box-shadow;
+    box-shadow: global.$standard-box-shadow;
     // transition: 0.2s;
     opacity: 0.3;
     &:hover {
@@ -646,7 +645,7 @@ footnote::before {
 }
 
 @media only screen and (max-width: 1000px) {
-    @import '../../global/globalCssVariables.module.scss';
+    // @import '../../global/globalCssVariables.module.scss';
 
     .ProseMirror {
         width: 100%;
@@ -664,7 +663,7 @@ footnote::before {
         padding: 0;
         border-width: 0px;
         border-radius: inherit;
-        border-color: $medium-gray;
+        border-color: global.$medium-gray;
         box-sizing: border-box;
         background-color: inherit;
         border-style: solid;
@@ -1074,4 +1073,3 @@ footnote::before {
         }
     }
 }
-
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss
index d6ed5ebee..fcc816447 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.scss
+++ b/src/client/views/nodes/formattedText/RichTextMenu.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.module.scss';
+@use '../../global/globalCssVariables.module.scss' as global;
 
 .button-dropdown-wrapper {
     position: relative;
@@ -25,7 +25,7 @@
         top: 35px;
         left: 0;
         background-color: #323232;
-        color: $light-gray;
+        color: global.$light-gray;
         border: 1px solid #4d4d4d;
         border-radius: 0 6px 6px 6px;
         box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 3c0ab3da5..657e689bb 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -610,7 +610,9 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                             <Slider
                                 sx={{
                                     '& input[type="range"]': {
-                                        WebkitAppearance: 'slider-vertical',
+                                        writingMode: 'vertical-lr',
+                                        direction: 'rtl',
+                                        // WebkitAppearance: 'slider-vertical',
                                     },
                                 }}
                                 orientation="vertical"
@@ -626,7 +628,9 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                             <Slider
                                 sx={{
                                     '& input[type="range"]': {
-                                        WebkitAppearance: 'slider-vertical',
+                                        writingMode: 'vertical-lr',
+                                        direction: 'rtl',
+                                        // WebkitAppearance: 'slider-vertical',
                                     },
                                 }}
                                 orientation="vertical"
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index e34e1b380..e24b47bd1 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -1,4 +1,4 @@
-@import '../../global/globalCssVariables.module.scss';
+@use '../../global/globalCssVariables.module.scss' as global;
 
 .presBox-gpt-chat {
     padding: 16px;
@@ -203,8 +203,8 @@
         align-items: center;
         height: 30px;
         width: 100%;
-        color: $white;
-        background-color: $dark-gray;
+        color: global.$white;
+        background-color: global.$dark-gray;
 
         .toolbar-button {
             cursor: pointer;
@@ -218,7 +218,7 @@
         }
 
         .toolbar-button.active {
-            color: $light-blue;
+            color: global.$light-blue;
             background-color: white;
             border-radius: 100%;
         }
@@ -266,7 +266,7 @@
     }
 
     .toolbar-divider {
-        border-left: solid $medium-gray 0.5px;
+        border-left: solid global.$medium-gray 0.5px;
         height: 20px;
     }
 }
@@ -274,13 +274,13 @@
 .dropdown {
     font-size: 10;
     margin-left: 5px;
-    color: $medium-gray;
+    color: global.$medium-gray;
     transition: 0.5s ease;
 }
 
 .dropdown.active {
     transform: rotate(180deg);
-    color: $light-blue;
+    color: global.$light-blue;
     opacity: 0.7;
 }
 
@@ -340,7 +340,7 @@
 
     .ribbon-colorBox {
         cursor: pointer;
-        border: solid 1px $black;
+        border: solid 1px global.$black;
         display: flex;
         margin-left: 5px;
         margin-top: 5px;
@@ -387,7 +387,7 @@
         }
 
         .ribbon-propertyUpDownItem:hover {
-            background: $medium-gray;
+            background: global.$medium-gray;
             transform: scale(1.05);
         }
     }
@@ -413,7 +413,7 @@
     @media screen and (-webkit-min-device-pixel-ratio: 0) {
         .multiThumb-slider {
             display: grid;
-            background-color: $white;
+            background-color: global.$white;
             height: 10px;
             border-radius: 10px;
             overflow: hidden;
@@ -431,8 +431,8 @@
                 -webkit-appearance: none;
                 height: 10px;
                 cursor: ew-resize;
-                background: $medium-blue;
-                box-shadow: -100vw 0 0 100vw $white;
+                background: global.$medium-blue;
+                box-shadow: -100vw 0 0 100vw global.$white;
             }
 
             .toolbar-slider.end::-webkit-slider-thumb {
@@ -441,8 +441,8 @@
                 -webkit-appearance: none;
                 height: 10px;
                 cursor: ew-resize;
-                background: $medium-blue;
-                box-shadow: -100vw 0 0 100vw $light-blue;
+                background: global.$medium-blue;
+                box-shadow: -100vw 0 0 100vw global.$light-blue;
             }
         }
 
@@ -456,7 +456,7 @@
             height: 10px;
             border-radius: 10px;
             -webkit-appearance: none;
-            background-color: $white;
+            background-color: global.$white;
         }
 
         .toolbar-slider:focus {
@@ -476,8 +476,8 @@
             -webkit-appearance: none;
             height: 10px;
             cursor: ew-resize;
-            background: $medium-blue;
-            box-shadow: -100vw 0 0 100vw $light-blue;
+            background: global.$medium-blue;
+            box-shadow: -100vw 0 0 100vw global.$light-blue;
         }
 
         .presBox-checkbox {
@@ -493,7 +493,7 @@
             width: 15px;
             min-width: 15px;
             cursor: pointer;
-            background: $white;
+            background: global.$white;
         }
 
         .presBox-checkbox:focus {
@@ -501,11 +501,11 @@
         }
 
         .presBox-checkbox:hover {
-            background: $light-gray;
+            background: global.$light-gray;
         }
 
         .presBox-checkbox:checked {
-            background: $light-blue;
+            background: global.$light-blue;
         }
     }
 
@@ -554,9 +554,9 @@
         text-align: center;
         font-size: 16;
         width: 90%;
-        color: $black;
+        color: global.$black;
         transform: translate(5%, 0px);
-        border-bottom: solid 2px $medium-gray;
+        border-bottom: solid 2px global.$medium-gray;
     }
 
     .ribbon-textInput {
@@ -568,8 +568,8 @@
         justify-self: left;
         margin-top: 5px;
         padding-left: 10px;
-        background-color: $white;
-        border: solid 1px $black;
+        background-color: global.$white;
+        border: solid 1px global.$black;
         min-width: 80px;
         max-width: 200px;
         width: 100%;
@@ -590,7 +590,7 @@
     }
 
     .ribbon-frameSelector {
-        border: $black solid 1px;
+        border: global.$black solid 1px;
         width: 60px;
         height: 20px;
         margin-top: 5px;
@@ -607,12 +607,12 @@
             cursor: pointer;
             position: relative;
             height: 100%;
-            background: $white;
+            background: global.$white;
             display: flex;
             align-items: center;
             justify-content: center;
             text-align: center;
-            color: $black;
+            color: global.$black;
         }
 
         .numKeyframe {
@@ -620,7 +620,7 @@
             font-size: 10;
             font-weight: 600;
             position: relative;
-            color: $black;
+            color: global.$black;
             display: flex;
             width: 100%;
             height: 100%;
@@ -662,7 +662,7 @@
             padding-left: 10;
             padding-right: 10;
             border-radius: 10px;
-            background-color: $medium-gray;
+            background-color: global.$medium-gray;
         }
 
         .ribbon-final-button:hover {
@@ -681,13 +681,13 @@
             align-items: center;
             margin-bottom: 5px;
             height: 25px;
-            color: $light-gray;
+            color: global.$light-gray;
             width: 100%;
             max-width: 120;
             padding-left: 10;
             padding-right: 10;
             border-radius: 10px;
-            background-color: $black;
+            background-color: global.$black;
         }
 
         .ribbon-final-button-hidden:hover {
@@ -698,15 +698,15 @@
         .ribbon-frameList {
             width: calc(100% - 5px);
             height: 50px;
-            background-color: $white;
-            border: 1px solid $medium-gray;
+            background-color: global.$white;
+            border: 1px solid global.$medium-gray;
             grid-template-rows: max-content;
 
             .frameList-header {
                 display: grid;
                 width: 100%;
                 height: 20px;
-                background-color: $medium-gray;
+                background-color: global.$medium-gray;
 
                 .frameList-headerButtons {
                     display: flex;
@@ -761,7 +761,7 @@
         font-size: 10.5;
         font-weight: 300;
         height: 20;
-        background-color: $medium-gray;
+        background-color: global.$medium-gray;
         color: white;
         display: flex;
         margin-top: 5px;
@@ -780,8 +780,8 @@
         transition: all 0.4s;
         font-weight: 400;
         opacity: 1;
-        color: $white;
-        background-color: $black;
+        color: global.$white;
+        background-color: global.$black;
     }
 
     .ribbon-toggle {
@@ -789,9 +789,9 @@
         font-size: 10.5;
         font-weight: 200;
         height: 20;
-        background-color: $white;
+        background-color: global.$white;
         display: inline-flex;
-        color: $black;
+        color: global.$black;
         border-radius: 5px;
         width: max-content;
         justify-content: center;
@@ -831,13 +831,13 @@
         position: relative;
         font-size: 13;
         padding-bottom: 10px;
-        border-bottom: solid 1px $dark-gray;
+        border-bottom: solid 1px global.$dark-gray;
 
         .presBox-dropdown:hover {
-            border: solid 1px $medium-blue;
+            border: solid 1px global.$medium-blue;
 
             .presBox-dropdownIcon {
-                color: $medium-blue;
+                color: global.$medium-blue;
             }
         }
 
@@ -846,12 +846,12 @@
             display: grid;
             grid-template-columns: auto 20%;
             position: relative;
-            border: solid 1px $black;
-            background-color: $light-gray;
+            border: solid 1px global.$black;
+            background-color: global.$light-gray;
             border-radius: 5px;
             font-size: 10;
             height: 25;
-            color: $black;
+            color: global.$black;
             padding-left: 5px;
             align-items: center;
             margin-top: 5px;
@@ -917,7 +917,7 @@
     height: 100px;
     padding-top: 5px;
     padding-bottom: 5px;
-    border: solid 1px $black;
+    border: solid 1px global.$black;
     // overflow: auto;
 
     ::-webkit-scrollbar {
@@ -967,7 +967,7 @@
     cursor: pointer;
     position: relative;
     text-align: center;
-    border-left: solid 1px $medium-gray;
+    border-left: solid 1px global.$medium-gray;
     width: 20%;
     height: 100%;
     display: flex;
@@ -998,7 +998,7 @@
     box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.8);
     z-index: 200;
     background-color: white;
-    color: $black;
+    color: global.$black;
     position: absolute;
     overflow: hidden;
 }
@@ -1014,12 +1014,12 @@
     align-items: center;
     justify-content: center;
     transform: translate(0px, -1px);
-    background-color: $white;
+    background-color: global.$white;
     width: 40px;
     height: 15px;
     align-self: center;
     justify-self: center;
-    border: solid 1px $black;
+    border: solid 1px global.$black;
     border-top: 0px;
     border-bottom-right-radius: 7px;
     border-bottom-left-radius: 7px;
@@ -1028,15 +1028,15 @@
 .layout-container {
     padding: 5px;
     display: grid;
-    background-color: $white;
+    background-color: global.$white;
     grid-template-columns: repeat(auto-fit, minmax(90px, 100px));
     width: 100%;
-    border: solid 1px $black;
+    border: solid 1px global.$black;
     min-width: 100px;
     overflow: hidden;
 
     .layout:hover {
-        border: solid 2px $medium-blue;
+        border: solid 2px global.$medium-blue;
     }
 
     .layout {
@@ -1051,7 +1051,7 @@
         width: 90px;
         overflow: hidden;
         background-color: white;
-        border: solid $medium-gray 1px;
+        border: solid global.$medium-gray 1px;
         display: grid;
         grid-template-rows: auto;
         align-items: center;
@@ -1066,7 +1066,7 @@
             height: 13;
             font-size: 12;
             display: flex;
-            background-color: $white;
+            background-color: global.$white;
         }
 
         .subtitle {
@@ -1079,7 +1079,7 @@
             height: 13;
             font-size: 9;
             display: flex;
-            background-color: $white;
+            background-color: global.$white;
         }
 
         .content {
@@ -1092,7 +1092,7 @@
             height: 13;
             font-size: 10;
             display: flex;
-            background-color: $white;
+            background-color: global.$white;
             height: 33;
             text-align: left;
             font-size: 8px;
@@ -1103,7 +1103,7 @@
 .presBox-buttons {
     position: relative;
     width: 100%;
-    background: $medium-gray;
+    background: global.$medium-gray;
     min-height: 35px;
     padding-top: 5px;
     padding-bottom: 5px;
@@ -1137,8 +1137,8 @@
     }
 
     select {
-        background: $dark-gray;
-        color: $white;
+        background: global.$dark-gray;
+        color: global.$white;
     }
 
     .presBox-button {
@@ -1152,8 +1152,8 @@
         text-align: center;
         letter-spacing: normal;
         width: inherit;
-        background: $dark-gray;
-        color: $white;
+        background: global.$dark-gray;
+        color: global.$white;
     }
 
     .presBox-button.active {
@@ -1161,7 +1161,7 @@
     }
 
     .presBox-button.active:hover {
-        background-color: $medium-blue;
+        background-color: global.$medium-blue;
     }
 
     .presBox-button.edit {
@@ -1238,8 +1238,8 @@
             font-size: 100;
             display: flex;
             align-items: center;
-            background: $dark-gray;
-            color: $white;
+            background: global.$dark-gray;
+            color: global.$white;
         }
 
         .presBox-viewPicker {
@@ -1273,7 +1273,7 @@
     left: 0;
     opacity: 0.5;
     transition: all 0.4s;
-    color: $white;
+    color: global.$white;
     width: 100%;
     height: 100%;
 }
@@ -1283,8 +1283,8 @@
 }
 
 .presPanelOverlay {
-    background-color: $dark-gray;
-    color: $white;
+    background-color: global.$dark-gray;
+    color: global.$white;
     border-radius: 5px;
     grid-template-rows: 100%;
     height: 100%;
@@ -1316,7 +1316,7 @@
     .presPanel-divider {
         width: 0.5px;
         height: 80%;
-        border-right: solid 1px $medium-gray;
+        border-right: solid 1px global.$medium-gray;
     }
 
     .presPanel-button-frame {
@@ -1348,12 +1348,12 @@
     }
 
     .presPanel-button:hover {
-        background-color: $medium-gray;
+        background-color: global.$medium-gray;
         transform: scale(1.2);
     }
 
     .presPanel-button-text:hover {
-        background-color: $medium-gray;
+        background-color: global.$medium-gray;
     }
 }
 
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 94e64b952..030d56f0c 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -1,4 +1,4 @@
-@import '../global/globalCssVariables.module.scss';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .searchBox-container {
     width: 100%;
@@ -22,7 +22,7 @@
         top: 0px;
         position: sticky;
         overflow-y: scroll;
-        border-bottom: $standard-border;
+        border-bottom: global.$standard-border;
 
         .searchBox-type {
             display: block;
@@ -48,20 +48,20 @@
 
     .section-header {
         .section-title {
-            font-size: $body-text;
+            font-size: global.$body-text;
             font-weight: 600;
         }
 
         .section-subtitle {
             display: flex;
-            color: $light-gray;
+            color: global.$light-gray;
         }
 
         padding: 5px 10px;
         display: flex;
         flex-direction: column;
         gap: 3px;
-        background: $medium-blue;
+        background: global.$medium-blue;
         color: white;
     }
 
diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss
index 20245104e..35a3da312 100644
--- a/src/client/views/topbar/TopBar.scss
+++ b/src/client/views/topbar/TopBar.scss
@@ -1,4 +1,5 @@
-@import '../global/globalCssVariables.module.scss';
+@use 'sass:color';
+@use '../global/globalCssVariables.module.scss' as global;
 
 .topbar-container {
     flex-direction: column;
@@ -6,13 +7,13 @@
     line-height: 1;
     overflow-y: auto;
     overflow-x: visible;
-    background: $dark-gray;
+    background: global.$dark-gray;
     overflow: visible;
     z-index: 1000;
     align-items: center;
-    height: $topbar-height;
-    background-color: $dark-gray;
-    border-bottom: $standard-border;
+    height: global.$topbar-height;
+    background-color: global.$dark-gray;
+    border-bottom: global.$standard-border;
     padding: 0px 10px;
     cursor: default;
     display: flex;
@@ -34,7 +35,7 @@
     }
 
     .topbar-button-text {
-        color: $white;
+        color: global.$white;
         padding: 10px;
         size: 15;
 
@@ -54,16 +55,16 @@
         align-self: center;
         padding: 5px;
         transition: linear 0.2s;
-        color: $white;
+        color: global.$white;
 
         &:hover {
-            background-color: darken($color: $light-gray, $amount: 20);
+            background-color: color.adjust(global.$light-gray, $lightness: -20%);
             font-weight: 500;
         }
     }
 
     .topbar-title {
-        color: $white;
+        color: global.$white;
         font-size: 17;
         font-weight: 500;
     }
@@ -119,7 +120,7 @@
         }
 
         .topBar-icon:hover {
-            background-color: $close-red;
+            background-color: global.$close-red;
         }
 
         .topbar-lozenge-user,
@@ -180,7 +181,7 @@
         &.topbar-input {
             margin: 5px;
             border-radius: 20px;
-            border: $dark-gray;
+            border: global.$dark-gray;
             display: block;
             width: 130px;
             -webkit-transition: width 0.4s;
@@ -212,8 +213,8 @@
         }
 
         &.topbar-close {
-            color: $white;
-            max-height: $topbar-height;
+            color: global.$white;
+            max-height: global.$topbar-height;
         }
     }
 }
@@ -229,7 +230,7 @@
 
     .no-result {
         width: 500px;
-        background: $light-gray;
+        background: global.$light-gray;
         padding: 10px;
         height: 50px;
         text-transform: uppercase;
-- 
cgit v1.2.3-70-g09d2


From 0e7ae057264445ece675e4b5d2380893ea124112 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 28 Feb 2025 15:40:51 -0500
Subject: fixed gptpopup disappearing in overlayView.  added creation of images
 based on selection image in gptpopup.

---
 src/client/views/LightboxView.tsx          | 137 +++++++++++++++--------------
 src/client/views/MainView.tsx              |   1 -
 src/client/views/OverlayView.tsx           |  11 +++
 src/client/views/nodes/ImageBox.tsx        |   2 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx |  21 +++--
 5 files changed, 97 insertions(+), 75 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 0d3feb073..0be847281 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -265,75 +265,80 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                 />
             </div>
         );
-        return !this._doc ? (
-            <OverlayView />
-        ) : (
-            <div
-                className="lightboxView-frame"
-                style={{ background: SnappingManager.userBackgroundColor }}
-                onPointerDown={e => {
-                    downx = e.clientX;
-                    downy = e.clientY;
-                }}
-                onClick={e => ClientUtils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
-                <div
-                    className="lightboxView-contents"
-                    style={{
-                        left: this.leftBorder,
-                        top: this.topBorder,
-                        width: this.lightboxWidth(),
-                        height: this.lightboxHeight(),
-                        // clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
-                        background: SnappingManager.userBackgroundColor,
-                    }}>
-                    <GestureOverlay isActive>
-                        <DocumentView
-                            key={this._doc[Id]} // this makes a new DocumentView when the document changes which makes link following work, otherwise no DocView is registered for the new Doc
-                            ref={action((r: DocumentView | null) => {
-                                this._docView = r !== null ? r : undefined;
-                            })}
-                            Document={this._doc}
-                            PanelWidth={this.lightboxWidth}
-                            PanelHeight={this.lightboxHeight}
-                            LayoutTemplate={this.lightboxDocTemplate}
-                            isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected.
-                            isContentActive={returnTrue}
-                            styleProvider={DefaultStyleProvider}
-                            ScreenToLocalTransform={this.lightboxScreenToLocal}
-                            renderDepth={0}
-                            suppressSetHeight={!!this._doc._layout_fitWidth}
-                            containerViewPath={returnEmptyDocViewList}
-                            childFilters={returnEmptyFilter}
-                            childFiltersByRanges={returnEmptyFilter}
-                            searchFilterDocs={returnEmptyDoclist}
-                            addDocument={undefined}
-                            removeDocument={undefined}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            addDocTab={this.AddDocTab}
-                            pinToPres={DocumentView.PinDoc}
-                            focus={emptyFunction}
-                        />
-                    </GestureOverlay>
+        return (
+            <>
+                <div style={{ display: this._doc ? 'none' : undefined }}>
+                    <OverlayView />
                 </div>
+                {!this._doc ? null : (
+                    <div
+                        className="lightboxView-frame"
+                        style={{ background: SnappingManager.userBackgroundColor }}
+                        onPointerDown={e => {
+                            downx = e.clientX;
+                            downy = e.clientY;
+                        }}
+                        onClick={e => ClientUtils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}>
+                        <div
+                            className="lightboxView-contents"
+                            style={{
+                                left: this.leftBorder,
+                                top: this.topBorder,
+                                width: this.lightboxWidth(),
+                                height: this.lightboxHeight(),
+                                // clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`,
+                                background: SnappingManager.userBackgroundColor,
+                            }}>
+                            <GestureOverlay isActive>
+                                <DocumentView
+                                    key={this._doc[Id]} // this makes a new DocumentView when the document changes which makes link following work, otherwise no DocView is registered for the new Doc
+                                    ref={action((r: DocumentView | null) => {
+                                        this._docView = r !== null ? r : undefined;
+                                    })}
+                                    Document={this._doc}
+                                    PanelWidth={this.lightboxWidth}
+                                    PanelHeight={this.lightboxHeight}
+                                    LayoutTemplate={this.lightboxDocTemplate}
+                                    isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected.
+                                    isContentActive={returnTrue}
+                                    styleProvider={DefaultStyleProvider}
+                                    ScreenToLocalTransform={this.lightboxScreenToLocal}
+                                    renderDepth={0}
+                                    suppressSetHeight={!!this._doc._layout_fitWidth}
+                                    containerViewPath={returnEmptyDocViewList}
+                                    childFilters={returnEmptyFilter}
+                                    childFiltersByRanges={returnEmptyFilter}
+                                    searchFilterDocs={returnEmptyDoclist}
+                                    addDocument={undefined}
+                                    removeDocument={undefined}
+                                    whenChildContentsActiveChanged={emptyFunction}
+                                    addDocTab={this.AddDocTab}
+                                    pinToPres={DocumentView.PinDoc}
+                                    focus={emptyFunction}
+                                />
+                            </GestureOverlay>
+                        </div>
 
-                {this._showPalette && <StickerPalette ref={r => (this._annoPaletteView = r)} Document={DocCast(Doc.UserDoc().myLightboxDrawings)} />}
-                {this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length ? true : false, this.previous)}
-                {this.renderNavBtn(
-                    this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
-                    undefined,
-                    this._props.PanelHeight / 2 - 12.5,
-                    'chevron-right',
-                    this._doc && this._future.length ? true : false,
-                    this.next,
-                    this.future().length.toString()
+                        {this._showPalette && <StickerPalette ref={r => (this._annoPaletteView = r)} Document={DocCast(Doc.UserDoc().myLightboxDrawings)} />}
+                        {this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length ? true : false, this.previous)}
+                        {this.renderNavBtn(
+                            this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]),
+                            undefined,
+                            this._props.PanelHeight / 2 - 12.5,
+                            'chevron-right',
+                            this._doc && this._future.length ? true : false,
+                            this.next,
+                            this.future().length.toString()
+                        )}
+                        <LightboxTourBtn lightboxDoc={this.lightboxDoc} navBtn={this.renderNavBtn} future={this.future} stepInto={this.stepInto} />
+                        {toggleBtn('lightboxView-navBtn', 'toggle reading view', BoolCast(this._doc?._layout_fitWidth), 'book-open', 'book', this.toggleFitWidth)}
+                        {toggleBtn('lightboxView-tabBtn', 'open document in a tab', false, 'file-export', '', this.downloadDoc)}
+                        {toggleBtn('lightboxView-paletteBtn', 'toggle sticker palette', this._showPalette === true, 'palette', '', this.togglePalette)}
+                        {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Ink, 'pen', '', this.togglePen)}
+                        {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', SnappingManager.ExploreMode, 'globe-americas', '', this.toggleExplore)}
+                    </div>
                 )}
-                <LightboxTourBtn lightboxDoc={this.lightboxDoc} navBtn={this.renderNavBtn} future={this.future} stepInto={this.stepInto} />
-                {toggleBtn('lightboxView-navBtn', 'toggle reading view', BoolCast(this._doc?._layout_fitWidth), 'book-open', 'book', this.toggleFitWidth)}
-                {toggleBtn('lightboxView-tabBtn', 'open document in a tab', false, 'file-export', '', this.downloadDoc)}
-                {toggleBtn('lightboxView-paletteBtn', 'toggle sticker palette', this._showPalette === true, 'palette', '', this.togglePalette)}
-                {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Ink, 'pen', '', this.togglePen)}
-                {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', SnappingManager.ExploreMode, 'globe-americas', '', this.toggleExplore)}
-            </div>
+            </>
         );
     }
 }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8b0354471..631c10b89 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -169,7 +169,6 @@ export class MainView extends ObservableReactComponent<object> {
     mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight();
 
     componentDidMount() {
-        OverlayView.Instance.addWindow(<GPTPopup />, { x: 400, y: 200, width: 500, height: 400, title: 'GPT', backgroundColor: 'transparent', isHidden: () => !SnappingManager.ChatVisible, onClick: () => SnappingManager.SetChatVisible(false) });
         // Utils.TraceConsoleLog();
         reaction(
             // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 20931fc3d..6686a162e 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -19,6 +19,7 @@ import './OverlayView.scss';
 import { DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider';
 import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
 import { SnappingManager } from '../util/SnappingManager';
+import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 
 export type OverlayDisposer = () => void;
 
@@ -126,6 +127,16 @@ export class OverlayView extends ObservableReactComponent<object> {
         makeObservable(this);
         if (!OverlayView.Instance) {
             OverlayView.Instance = this;
+            this.addWindow(<GPTPopup />, {
+                x: 400,
+                y: 200,
+                width: 500,
+                height: 400,
+                title: 'GPT', //
+                backgroundColor: 'transparent',
+                isHidden: () => !SnappingManager.ChatVisible,
+                onClick: () => SnappingManager.SetChatVisible(false),
+            });
             new ResizeObserver(
                 action(entries => {
                     Array.from(entries).forEach(entry => {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d122ca5b0..279317f49 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -36,7 +36,7 @@ import { OverlayView } from '../OverlayView';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PinDocView, PinProps } from '../PinFuncs';
 import { DrawingFillHandler } from '../smartdraw/DrawingFillHandler';
-import { FireflyImageData, FireflyImageDimensions, isFireflyImageData } from '../smartdraw/FireflyConstants';
+import { FireflyImageData, isFireflyImageData } from '../smartdraw/FireflyConstants';
 import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
 import { StickerPalette } from '../smartdraw/StickerPalette';
 import { StyleProp } from '../StyleProp';
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 96c7f37a3..79f5121ed 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -28,6 +28,8 @@ import './GPTPopup.scss';
 import { FireflyImageDimensions } from '../../smartdraw/FireflyConstants';
 import { Upload } from '../../../../server/SharedMediaTypes';
 import { OpenWhere } from '../../nodes/OpenWhere';
+import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler';
+import { ImageField } from '../../../../fields/URLField';
 
 export enum GPTPopupMode {
     SUMMARY, // summary of seleted document text
@@ -204,13 +206,18 @@ export class GPTPopup extends ObservableReactComponent<object> {
         )) // prettier-ignore
 
     generateFireflyImage = (imgDesc: string) => {
-        // if (this._fireflyRefStrength) {
-        //     DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title), this.Document)?.then(
-        //         action(() => {
-        //             this._regenerateLoading = false;
-        //         })
-        //     );
-        // }  else∂
+        const selView = DocumentView.Selected().lastElement();
+        const selDoc = selView?.Document;
+        if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutFieldKey(selDoc)] instanceof ImageField)) {
+            const oldPrompt = StrCast(selDoc.ai_firefly_prompt, StrCast(selDoc.title));
+            const newPrompt = oldPrompt ? `${oldPrompt} ~~~ ${imgDesc}` : imgDesc;
+            return DrawingFillHandler.drawingToImage(selDoc, 100, newPrompt, selDoc)
+                .then(action(() => (this._userPrompt = '')))
+                .catch(e => {
+                    alert(e);
+                    return undefined;
+                });
+        }
         return SmartDrawHandler.CreateWithFirefly(imgDesc, FireflyImageDimensions.Square, 0)
             .then(
                 action(doc => {
-- 
cgit v1.2.3-70-g09d2


From dbe3a3cf1944a69a3ce492481bc22b8f1a8d53e8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 4 Mar 2025 18:21:47 -0500
Subject: added link cretor button to text anchor menu

---
 src/client/util/SharingManager.tsx                        |  6 ------
 src/client/views/AntimodeMenu.scss                        |  1 -
 src/client/views/MainView.tsx                             |  2 --
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 +++++++++++++-
 src/client/views/pdf/AnchorMenu.tsx                       |  2 ++
 5 files changed, 15 insertions(+), 10 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index efc8e79a6..3a248400b 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -502,7 +502,6 @@ export class SharingManager extends React.Component<object> {
         }
     };
 
-    // eslint-disable-next-line react/sort-comp
     public close = action(() => {
         this.isOpen = false;
         this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
@@ -517,7 +516,6 @@ export class SharingManager extends React.Component<object> {
         this.layoutDocAcls = false;
     });
 
-    // eslint-disable-next-line react/no-unused-class-component-methods
     public open = (target?: DocumentView, targetDoc?: Doc) => {
         this.populateUsers();
         runInAction(() => {
@@ -534,7 +532,6 @@ export class SharingManager extends React.Component<object> {
      * @param group
      * @param emailId
      */
-    // eslint-disable-next-line react/no-unused-class-component-methods
     shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
         const user = this.users.find(({ user: { email } }) => email === emailId)!;
         if (group.docsShared) {
@@ -559,7 +556,6 @@ export class SharingManager extends React.Component<object> {
     /**
      * Called from the properties sidebar to change permissions of a user.
      */
-    // eslint-disable-next-line react/no-unused-class-component-methods
     shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
         if (layout) this.layoutDocAcls = true;
         if (shareWith !== 'Guest') {
@@ -583,7 +579,6 @@ export class SharingManager extends React.Component<object> {
      * @param group
      * @param emailId
      */
-    // eslint-disable-next-line react/no-unused-class-component-methods
     removeMember = (group: Doc, emailId: string) => {
         const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
 
@@ -607,7 +602,6 @@ export class SharingManager extends React.Component<object> {
      * Removes a group's permissions from documents that have been shared with it.
      * @param group
      */
-    // eslint-disable-next-line react/no-unused-class-component-methods
     removeGroup = (group: Doc) => {
         if (group.docsShared) {
             DocListCast(group.docsShared).forEach(doc => {
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
index 48fa86276..c2f6ae62d 100644
--- a/src/client/views/AntimodeMenu.scss
+++ b/src/client/views/AntimodeMenu.scss
@@ -6,7 +6,6 @@
     height: global.$antimodemenu-height;
     width: fit-content;
     border-radius: global.$standard-border-radius;
-    overflow: hidden;
     // box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
     // border-radius: 0px 6px 6px 6px;
     display: flex;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 631c10b89..cc7c1a42b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -75,10 +75,8 @@ import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
 import ImageEditorBox from './nodes/imageEditor/ImageEditor';
 import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
-import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { SmartDrawHandler } from './smartdraw/SmartDrawHandler';
 import { TopBar } from './topbar/TopBar';
-import { OverlayView } from './OverlayView';
 
 // eslint-disable-next-line @typescript-eslint/no-require-imports
 const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e9c521aa0..c2a2caecf 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1669,7 +1669,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             for (let target: HTMLElement | Element | null = clickTarget as HTMLElement; target instanceof HTMLElement && !target.dataset?.targethrefs; target = target.parentElement);
             while (clickTarget instanceof HTMLElement && !clickTarget.dataset?.targethrefs) clickTarget = clickTarget.parentElement;
             const dataset = clickTarget instanceof HTMLElement ? clickTarget?.dataset : undefined;
-            FormattedTextBoxComment.update(this, this.EditorView!, undefined, dataset?.targethrefs, dataset?.linkdoc, dataset?.nopreview === 'true');
+
+            if (dataset?.targethrefs)
+                window
+                    .open(
+                        dataset?.targethrefs
+                            ?.trim()
+                            .split(' ')
+                            .filter(h => h)
+                            .lastElement(),
+                        '_blank'
+                    )
+                    ?.focus();
+            else FormattedTextBoxComment.update(this, this.EditorView!, undefined, dataset?.targethrefs, dataset?.linkdoc, dataset?.nopreview === 'true');
         }
     };
     @action
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 28371594e..9aa8fe649 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -18,6 +18,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
 import './AnchorMenu.scss';
 import { GPTPopup } from './GPTPopup/GPTPopup';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
@@ -241,6 +242,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             color={SettingsManager.userColor}
                         />
                     )}
+                    {this._selectedText && RichTextMenu.Instance?.createLinkButton()}
                     {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
                         <IconButton
                             tooltip="Click to Record Annotation" //
-- 
cgit v1.2.3-70-g09d2


From 84139cbfb20ab53fe42d1c21a74b86368c5822ca Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 4 Mar 2025 19:07:45 -0500
Subject: fixed maximizing mainview by fixing window event removal in
 dockingview.

---
 src/client/views/MainView.tsx                      |  2 +-
 .../views/collections/CollectionDockingView.tsx    | 30 ++++++++++++++--------
 2 files changed, 20 insertions(+), 12 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cc7c1a42b..afefe3f03 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -686,7 +686,7 @@ export class MainView extends ObservableReactComponent<object> {
         );
     }
     @computed get mainDocView() {
-        const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
+        const headerBar = null; // this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
         return (
             <>
                 {headerBar}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 539b49c86..04e3b2663 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -48,8 +48,7 @@ export class CollectionDockingView extends CollectionSubView() {
     // eslint-disable-next-line no-use-before-define
     @observable public static Instance: CollectionDockingView | undefined = undefined;
 
-    private _reactionDisposer?: IReactionDisposer;
-    private _lightboxReactionDisposer?: IReactionDisposer;
+    private _disposers: { [key: string]: IReactionDisposer } = {};
     private _containerRef = React.createRef<HTMLDivElement>();
     private _flush: UndoManager.Batch | undefined;
     private _unmounting = false;
@@ -66,6 +65,7 @@ export class CollectionDockingView extends CollectionSubView() {
     constructor(props: SubCollectionViewProps) {
         super(props);
         makeObservable(this);
+        console.log('CREATING DOCKING VIEW');
         if (this._props.renderDepth < 0) CollectionDockingView.Instance = this;
         // Why is this here?
         (window as unknown as { React: unknown }).React = React;
@@ -282,6 +282,7 @@ export class CollectionDockingView extends CollectionSubView() {
     }
     setupGoldenLayout = async () => {
         if (this._unmounting) return;
+        console.log('SETUP LAYOUT');
         // const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document)));
         const config = StrCast(this.Document.dockingConfig);
         if (config) {
@@ -339,29 +340,36 @@ export class CollectionDockingView extends CollectionSubView() {
     componentDidMount: () => void = async () => {
         this._props.setContentViewBox?.(this);
         this._unmounting = false;
+        console.log('MOUNTING');
         SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property
         if (this._containerRef.current) {
-            this._lightboxReactionDisposer = reaction(
+            this._disposers.lightbox = reaction(
                 () => DocumentView.LightboxDoc(),
                 doc => setTimeout(() => !doc && this.onResize())
             );
             new ResizeObserver(this.onResize).observe(this._containerRef.current);
-            this._reactionDisposer = reaction(
+            this._disposers.docking = reaction(
                 () => StrCast(this.Document.dockingConfig),
                 config => {
                     if (!this._goldenLayout || this._ignoreStateChange !== config) {
+                        console.log('CONFIG CHANGED');
                         //  bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one.
                         this.setupGoldenLayout();
                     }
                     this._ignoreStateChange = '';
                 }
             );
-            reaction(
+            this._disposers.panel = reaction(
                 () => this._props.PanelWidth(),
-                width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows
+                width => {
+                    if (!this._goldenLayout && width > 20) {
+                        console.log('PWIDTH = ' + width);
+                        setTimeout(() => this.setupGoldenLayout());
+                    }
+                }, // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows
                 { fireImmediately: true }
             );
-            reaction(
+            this._disposers.color = reaction(
                 () => [SnappingManager.userBackgroundColor, SnappingManager.userBackgroundColor],
                 () => {
                     clearStyleSheetRules(CollectionDockingView._highlightStyleSheet);
@@ -375,7 +383,9 @@ export class CollectionDockingView extends CollectionSubView() {
     };
 
     componentWillUnmount: () => void = () => {
+        console.log('UNMOUNTING');
         this._unmounting = true;
+        Object.values(this._disposers).forEach(d => d());
         try {
             this._goldenLayout.unbind('stackCreated', this.stackCreated);
             this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
@@ -385,9 +395,6 @@ export class CollectionDockingView extends CollectionSubView() {
         setTimeout(() => this._goldenLayout?.destroy());
         window.removeEventListener('resize', this.onResize);
         window.removeEventListener('mouseup', this.onPointerUp);
-
-        this._reactionDisposer?.();
-        this._lightboxReactionDisposer?.();
     };
 
     // ViewBoxInterface overrides
@@ -412,6 +419,7 @@ export class CollectionDockingView extends CollectionSubView() {
                   .map(f => f as Doc);
         const changesMade = this.Document.dockingConfig !== json;
         if (changesMade) {
+            console.log('WRITING CONFIG');
             if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) {
                 this.layoutDoc.dockingConfig = json;
                 this.layoutDoc.data = new List<Doc>(docs);
@@ -426,7 +434,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
     @action
     onPointerUp = (): void => {
-        window.removeEventListener('pointerup', this.onPointerUp);
+        window.removeEventListener('mouseup', this.onPointerUp);
         DragManager.CompleteWindowDrag = undefined;
         setTimeout(this.endUndoBatch, 100);
     };
-- 
cgit v1.2.3-70-g09d2


From db3bdb19ff7bc1c69c544797c05a6db3b72b1464 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Mar 2025 14:54:18 -0500
Subject: added:hover mode for images to make primary image fade in instead of
 alternate.

---
 src/client/documents/Documents.ts                  |  3 ++
 src/client/views/MainView.tsx                      |  2 +-
 src/client/views/TagsView.tsx                      |  3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.scss           |  1 +
 src/client/views/nodes/DocumentView.tsx            | 16 +++++----
 src/client/views/nodes/ImageBox.scss               |  6 ++++
 src/client/views/nodes/ImageBox.tsx                | 40 +++++++++++++++-------
 .../views/nodes/chatbot/tools/ImageCreationTool.ts |  3 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  3 ++
 src/client/views/smartdraw/DrawingFillHandler.tsx  |  4 +++
 src/client/views/smartdraw/SmartDrawHandler.tsx    |  8 +++--
 12 files changed, 65 insertions(+), 26 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1ce25165c..21d3c978b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -384,6 +384,9 @@ export class DocumentOptions {
     presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view', false);
     presentation_zoomText?: BOOLt = new BoolInfo('whether text anchors should shown in a larger box when following links to make them stand out', false);
 
+    data_annotations?: List<Doc>;
+    _data_usePath?: STRt = new StrInfo("description of field key to display in image box ('alternate','alternate:hover', 'data:hover'). defaults to primary", false);
+    data_alternates?: List<Doc>;
     data?: FieldType;
     data_useCors?: BOOLt = new BoolInfo('whether CORS protocol should be used for web page');
     _face_showImages?: BOOLt = new BoolInfo('whether to show images in uniqe face Doc');
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index afefe3f03..cc7c1a42b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -686,7 +686,7 @@ export class MainView extends ObservableReactComponent<object> {
         );
     }
     @computed get mainDocView() {
-        const headerBar = null; // this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
+        const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
         return (
             <>
                 {headerBar}
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index b70e21918..93d6fb684 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -398,8 +398,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                                         e.stopPropagation();
                                     }}
                                     type="text"
-                                    placeholder="Input tags for document..."
-                                    aria-label="tagsView-input"
+                                    placeholder="Enter #tags or @metadata"
                                     className="tagsView-input"
                                     style={{ width: '100%', borderRadius: '5px' }}
                                 />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b3d908da4..43addfc29 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -344,7 +344,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     focusOnPoint = (options: FocusViewOptions) => {
         const { pointFocus, zoomTime, didMove } = options;
         if (!this.Document.isGroup && pointFocus && !didMove) {
-            const dfltScale = this.isAnnotationOverlay ? 1 : 0.5;
+            const dfltScale = this.isAnnotationOverlay ? 1 : 0.25;
             if (this.layoutDoc[this.scaleFieldKey] !== dfltScale) {
                 this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(pointFocus.X, pointFocus.Y), dfltScale, zoomTime);
                 options.didMove = true;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 294af4d96..dd5fd0d0c 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -291,6 +291,7 @@
     justify-items: center;
     background-color: rgb(223, 223, 223);
     transform-origin: top left;
+    background: transparent;
 
     .documentView-editorView-resizer {
         height: 5px;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5f5dd1210..595abc7f8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -279,16 +279,17 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined
     };
     onBrowseClick = (e: React.MouseEvent) => {
-        const browseTransitionTime = 500;
+        //const browseTransitionTime = 500;
         DocumentView.DeselectAll();
         DocumentView.showDocument(this.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => {
-            const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime };
+            // const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime };
             if (!focused && this._docView) {
-                this._docView
-                    .docViewPath()
-                    .reverse()
-                    .forEach(cont => cont.ComponentView?.focus?.(cont.Document, options));
-                Doc.linkFollowHighlight(this.Document, false);
+                DocumentView.showDocument(this.Document, { zoomScale: 0.3, willZoomCentered: true });
+                // this._docView
+                //     .docViewPath()
+                //     .reverse()
+                //     .forEach(cont => cont.ComponentView?.focus?.(cont.Document, options));
+                // Doc.linkFollowHighlight(this.Document, false);
             }
         });
         e.stopPropagation();
@@ -797,6 +798,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                         <div
                             className="documentView-editorView"
                             style={{
+                                background: SnappingManager.userVariantColor,
                                 width: `${100 / this.uiBtnScaling}%`, //
                                 transform: `scale(${this.uiBtnScaling})`,
                             }}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 15581a9e8..3d6942e6f 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -40,6 +40,7 @@
     max-height: 100%;
     pointer-events: inherit;
     background: transparent;
+    z-index: 0;
     // z-index: -10000; // bcz: not sure why this was here.  it broke dropping images on the image box alternate bullseye icon.
 
     img {
@@ -103,6 +104,10 @@
     margin: 0 auto;
     display: flex;
     height: 100%;
+    img {
+        object-fit: contain;
+        height: 100%;
+    }
 
     .imageBox-fadeBlocker,
     .imageBox-fadeBlocker-hover {
@@ -237,5 +242,6 @@
         text-overflow: ellipsis;
         max-width: 65%;
         width: 100%;
+        color: black;
     }
 }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4979a4d4d..017ef7191 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -436,6 +436,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     };
     getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
 
+    @computed get usingAlternate() {
+        const usePath = StrCast(this.Document[this.fieldKey + '_usePath']);
+        return 'alternate' === usePath || ('alternate:hover' === usePath && this._isHovering) || (':hover' === usePath && !this._isHovering);
+    }
+
     @computed get nativeSize() {
         TraceMobx();
         if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 };
@@ -471,10 +476,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}>
                             <em>alternate, </em>
                         </span>
-                        and show
                         <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}>
                             <em> alternate on hover</em>
                         </span>
+                        and show
+                        <span style={{ color: usePath === ':hover' ? 'black' : undefined }}>
+                            <em> primary on hover</em>
+                        </span>
                     </div>
                 }>
                 <div
@@ -482,7 +490,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     ref={this._overlayIconRef}
                     onPointerDown={e =>
                         setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => {
-                            this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined;
+                            this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : usePath === 'alternate:hover' ? ':hover' : undefined;
                         })
                     }
                     style={{
@@ -527,7 +535,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 .filter(url => url)
                 .map(url => this.choosePath(url)) ?? []; // acc  ess the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
-        return paths.length ? paths : [defaultUrl.href];
+        return paths.length ? paths.reverse() : [defaultUrl.href];
     }
 
     @computed get content() {
@@ -552,7 +560,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             transformOrigin = 'right top';
             transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
         }
-        const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`];
 
         return (
             <div
@@ -571,7 +578,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         ref={action((r: HTMLImageElement | null) => (this.imageRef = r))}
                         key="paths"
                         src={srcpath}
-                        style={{ transform, transformOrigin, objectFit: 'fill', height: '100%' }}
+                        style={{ transform, transformOrigin }}
                         onError={action(e => {
                             this._error = e.toString();
                         })}
@@ -579,7 +586,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         width={nativeWidth}
                     />
                     {fadepath === srcpath ? null : (
-                        <div className={`imageBox-fadeBlocker${(this._isHovering && usePath === 'alternate:hover') || usePath === 'alternate' ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
+                        <div className={`imageBox-fadeBlocker${this.usingAlternate ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
                             <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
                         </div>
                     )}
@@ -619,8 +626,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return (
             <div className="imageBox-aiView">
                 <div className="imageBox-aiView-regenerate">
-                    <span className="imageBox-aiView-firefly">Firefly:</span>
+                    <span className="imageBox-aiView-firefly" style={{ color: SnappingManager.userColor }}>
+                        Firefly:
+                    </span>
                     <input
+                        style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
                         className="imageBox-aiView-input"
                         aria-label="Edit instructions input"
                         type="text"
@@ -631,7 +641,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     <div className="imageBox-aiView-regenerate-createBtn">
                         <Button
                             text="Create"
-                            type={Type.SEC}
+                            type={Type.TERT}
+                            color={SnappingManager.userColor}
+                            background={SnappingManager.userBackgroundColor}
                             // style={{ alignSelf: 'flex-end' }}
                             icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
                             iconPlacement="right"
@@ -663,13 +675,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     </div>
                 </div>
                 <div className="imageBox-aiView-strength">
-                    <span className="imageBox-aiView-similarity">Similarity</span>
+                    <span className="imageBox-aiView-similarity" style={{ color: SnappingManager.userColor }}>
+                        Similarity
+                    </span>
                     <Slider
                         className="imageBox-aiView-slider"
                         sx={{
-                            '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
+                            '& .MuiSlider-track': { color: SettingsManager.userColor },
                             '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor },
-                            '& .MuiSlider-thumb': { color: SettingsManager.userVariantColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
+                            '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
                         }}
                         min={0}
                         max={100}
@@ -725,11 +739,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return { width, height };
     };
     savedAnnotations = () => this._savedAnnotations;
-
     render() {
         TraceMobx();
         const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string;
         const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
+        const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']);
+        const doc = this.usingAlternate ? (alts.lastElement() ?? this.Document) : this.Document;
         return (
             <div
                 className="imageBox"
@@ -754,6 +769,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 <CollectionFreeFormView
                     ref={this._ffref}
                     {...this._props}
+                    Document={doc}
                     setContentViewBox={emptyFunction}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
diff --git a/src/client/views/nodes/chatbot/tools/ImageCreationTool.ts b/src/client/views/nodes/chatbot/tools/ImageCreationTool.ts
index e92d87dfd..37907fd4f 100644
--- a/src/client/views/nodes/chatbot/tools/ImageCreationTool.ts
+++ b/src/client/views/nodes/chatbot/tools/ImageCreationTool.ts
@@ -5,6 +5,7 @@ import { ParametersType, ToolInfo } from '../types/tool_types';
 import { Observation } from '../types/types';
 import { BaseTool } from './BaseTool';
 import { Upload } from '../../../../../server/SharedMediaTypes';
+import { List } from '../../../../../fields/List';
 
 const imageCreationToolParams = [
     {
@@ -41,7 +42,7 @@ export class ImageCreationTool extends BaseTool<ImageCreationToolParamsType> {
                 image_prompt,
             })) as { result: Upload.FileInformation & Upload.InspectionResults; url: string };
             console.log('Image generation result:', result);
-            this._createImage(result, { text: RTFCast(image_prompt) });
+            this._createImage(result, { text: RTFCast(image_prompt), ai: 'dall-e-3', tags: new List<string>(['@ai']) });
             return url
                 ? [
                       {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 79f5121ed..4dc45e6a0 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -30,6 +30,7 @@ import { Upload } from '../../../../server/SharedMediaTypes';
 import { OpenWhere } from '../../nodes/OpenWhere';
 import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler';
 import { ImageField } from '../../../../fields/URLField';
+import { List } from '../../../../fields/List';
 
 export enum GPTPopupMode {
     SUMMARY, // summary of seleted document text
@@ -352,6 +353,8 @@ export class GPTPopup extends ObservableReactComponent<object> {
                 y: NumCast(textAnchor.y),
                 _height: 200,
                 _width: 200,
+                ai: 'dall-e',
+                tags: new List<string>(['@ai']),
                 data_nativeWidth: 1024,
                 data_nativeHeight: 1024,
             });
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index 0a30b14b8..c672bc718 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -1,6 +1,7 @@
 import { imageUrlToBase64 } from '../../../ClientUtils';
 import { Doc, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
+import { List } from '../../../fields/List';
 import { DocCast, ImageCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { Upload } from '../../../server/SharedMediaTypes';
@@ -53,7 +54,10 @@ export class DrawingFillHandler {
                                         undefined,
                                         Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {
                                             ai: 'firefly',
+                                            tags: new List<string>(['@ai']),
                                             title: newPrompt,
+                                            _data_usePath: 'alternate:hover',
+                                            data_alternates: new List<Doc>([drawing]),
                                             ai_firefly_prompt: newPrompt,
                                             _width: 500,
                                             data_nativeWidth: info.nativeWidth,
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index ca308015d..1cceabed3 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -28,6 +28,7 @@ import { FireflyDimensionsMap, FireflyImageData, FireflyImageDimensions } from '
 import './SmartDrawHandler.scss';
 import { Upload } from '../../../server/SharedMediaTypes';
 import { PointData } from '../../../pen-gestures/GestureTypes';
+import { List } from '../../../fields/List';
 
 export interface DrawingOptions {
     text?: string;
@@ -293,15 +294,17 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                     return undefined;
                 }
                 const newseed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)?.[1];
-                const imgDoc: Doc = Docs.Create.ImageDocument(img.accessPaths.agnostic.client, {
+                return Docs.Create.ImageDocument(img.accessPaths.agnostic.client, {
                     title: input,
                     nativeWidth: dims.width,
                     nativeHeight: dims.height,
+                    tags: new List<string>(['@ai']),
+                    _width: Math.min(400, dims.width),
+                    _height: (Math.min(400, dims.width) * dims.height) / dims.width,
                     ai: 'firefly',
                     ai_firefly_seed: +(newseed ?? 0),
                     ai_firefly_prompt: input,
                 });
-                return imgDoc;
             })
             .catch(e => {
                 alert('create image failed: ' + e.toString());
@@ -568,6 +571,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                         color={SettingsManager.userColor}
                     />
                     <input
+                        style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
                         aria-label="Smart Draw Input"
                         className="smartdraw-input"
                         type="text"
-- 
cgit v1.2.3-70-g09d2


From 204132aba65d1c25ba6c256b3777c63e51c536f3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Mar 2025 19:36:07 -0500
Subject: fixed doccreatemenu warnings.

---
 src/client/views/MainView.tsx                      |   2 +-
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 350 +++++++++++----------
 2 files changed, 186 insertions(+), 166 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fddc0e40c..ef8d0c197 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1140,7 +1140,7 @@ export class MainView extends ObservableReactComponent<object> {
                 <PreviewCursor />
                 <TaskCompletionBox />
                 <ContextMenu />
-                <DocCreatorMenu />
+                <DocCreatorMenu addDocTab={DocumentViewInternal.addDocTabFunc} />
                 <ImageLabelHandler />
                 <SmartDrawHandler />
                 <AnchorMenu />
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 16d588c55..64416c26d 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Colors } from 'browndash-components';
+import { Colors } from '@dash/components';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { IDisposer } from 'mobx-utils';
@@ -20,7 +20,6 @@ import { UndoManager, undoable } from '../../../../util/UndoManager';
 import { ObservableReactComponent } from '../../../ObservableReactComponent';
 import { CollectionFreeFormView } from '../../../collections/collectionFreeForm/CollectionFreeFormView';
 import { DocumentView, DocumentViewInternal } from '../../DocumentView';
-import { FieldViewProps } from '../../FieldView';
 import { OpenWhere } from '../../OpenWhere';
 import { DataVizBox } from '../DataVizBox';
 import './DocCreatorMenu.scss';
@@ -64,22 +63,27 @@ export type Col = {
     defaultContent?: string;
 };
 
+interface DocCreateMenuProps {
+    addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
+}
+
 @observer
-export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
+export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> {
+    // eslint-disable-next-line no-use-before-define
     static Instance: DocCreatorMenu;
 
     private _disposers: { [name: string]: IDisposer } = {};
 
     private _ref: HTMLDivElement | null = null;
 
-    private templateManager: TemplateManager; 
+    private templateManager: TemplateManager;
 
     @observable _fullyRenderedDocs: Doc[] = [];
     @observable _renderedDocCollectionPreview: Doc | undefined = undefined;
     @observable _renderedDocCollection: Doc | undefined = undefined;
     @observable _docsRendering: boolean = false;
 
-    @observable _userTemplates: {template: Template, doc: Doc}[] = []; //!!! used to keep track of all templates, should be refactored to work with actual templates and not docs
+    @observable _userTemplates: { template: Template; doc: Doc }[] = []; //!!! used to keep track of all templates, should be refactored to work with actual templates and not docs
     @observable _selectedTemplate: Template | undefined = undefined;
     @observable _currEditingTemplate: Template | undefined = undefined;
 
@@ -92,7 +96,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     @observable _expandedPreview: Doc | undefined = undefined;
 
     @observable _suggestedTemplates: Template[] = [];
-    @observable _suggestedTemplatePreviews: {doc: Doc, template: Template}[] = [];
+    @observable _suggestedTemplatePreviews: { doc: Doc; template: Template }[] = [];
     @observable _GPTOpt: boolean = false;
     @observable _callCount: number = 0;
     @observable _GPTLoading: boolean = false;
@@ -111,7 +115,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     @observable _draggingIndicator: boolean = false;
     @observable _dataViz?: DataVizBox;
     @observable _interactionLock: boolean | undefined;
-    @observable _snapPt: {x: number, y: number} = {x: 0, y: 0};
+    @observable _snapPt: { x: number; y: number } = { x: 0, y: 0 };
     @observable _resizeHdlId: string = '';
     @observable _resizing: boolean = false;
     @observable _offset: { x: number; y: number } = { x: 0, y: 0 };
@@ -120,7 +124,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     @observable _menuDimensions: { width: number; height: number } = { width: 400, height: 400 };
     @observable _editing: boolean = false;
 
-    constructor(props: any) {
+    constructor(props: DocCreateMenuProps) {
         super(props);
         makeObservable(this);
         DocCreatorMenu.Instance = this;
@@ -138,14 +142,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         this._userCreatedFields = [];
     };
     @action addUserTemplate = (template: Template) => {
-        this._userTemplates.push({template: template.cloneBase(), doc: template.getRenderedDoc()});
+        this._userTemplates.push({ template: template.cloneBase(), doc: template.getRenderedDoc() });
     };
     @action removeUserTemplate = (template: Template) => {
         this._userTemplates = this._userTemplates.filter(info => info.template !== template);
-    }
+    };
     @action updateTemplatePreview = (template: Template) => {
         template.renderUpdates();
-        const preview = {template: template, doc: template.getRenderedDoc()};
+        const preview = { template: template, doc: template.getRenderedDoc() };
         this._suggestedTemplatePreviews = this._suggestedTemplatePreviews.map(t => { return t.template === preview.template ? preview : t }); //prettier-ignore
         this._userTemplates = this._userTemplates.map(t => { return t.template === preview.template ? preview : t }); //prettier-ignore
     };
@@ -216,7 +220,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         return bounds;
     }
 
-    setUpButtonClick = (e: any, func: () => void) => {
+    setUpButtonClick = (e: React.PointerEvent, func: () => void) => {
         setupMoveUpEvents(
             this,
             e,
@@ -298,7 +302,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
         e.stopPropagation();
         const id = (this._resizeHdlId = e.currentTarget.className);
-        const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as any).width.replace('px', '')) / 2 : 0;
+        const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as HTMLElement).width.replace('px', '')) / 2 : 0;
         const bounds = e.currentTarget.getBoundingClientRect();
         this._offset = {
             x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, //
@@ -309,7 +313,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     };
 
     @action
-    onResize = (e: any): boolean => {
+    onResize = (e: PointerEvent): boolean => {
         const dragHdl = this._resizeHdlId.split(' ')[1];
         const thisPt = DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y);
 
@@ -324,7 +328,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     };
 
     @action
-    onDrag = (e: any): boolean => {
+    onDrag = (e: PointerEvent): boolean => {
         this._pageX = e.pageX - (this._startPos?.x ?? 0);
         this._pageY = e.pageY - (this._startPos?.y ?? 0);
         this._initDimensions.x = this._pageX;
@@ -377,7 +381,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         } else {
             this._selectedTemplate = template;
             template.renderUpdates();
-            this._fullyRenderedDocs = await this.createDocsFromTemplate(template) ?? [];
+            this._fullyRenderedDocs = (await this.createDocsFromTemplate(template)) ?? [];
             this.updateRenderedDocCollection();
         }
     };
@@ -486,9 +490,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
      * @returns a doc containing the fully rendered template
      */
     applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise<Template | undefined> => {
-
-        const GPTTextCalls = Object.entries(assignments).filter(([str, col]) => col.type === TemplateFieldType.TEXT && this._userCreatedFields.includes(col));
-        const GPTIMGCalls = Object.entries(assignments).filter(([str, col]) => col.type === TemplateFieldType.VISUAL && this._userCreatedFields.includes(col));
+        const GPTTextCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.TEXT && this._userCreatedFields.includes(col));
+        const GPTIMGCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.VISUAL && this._userCreatedFields.includes(col));
 
         if (GPTTextCalls.length) {
             const promises = GPTTextCalls.map(([str, col]) => {
@@ -504,7 +507,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             });
 
             await Promise.all(promises);
-        };
+        }
 
         return template;
     };
@@ -557,7 +560,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     const toObj = Object.entries(assignment).reduce(
                         (a, [fieldID, colTitle]) => {
                             const col = this.getColByTitle(colTitle);
-                            if (!this._userCreatedFields.includes(col)){ // do the following for any fields not added by the user; will change in the future, for now only GPT content works with user-added fields
+                            if (!this._userCreatedFields.includes(col)) {
+                                // do the following for any fields not added by the user; will change in the future, for now only GPT content works with user-added fields
                                 const field = template.getFieldByID(Number(fieldID));
                                 field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? FieldContentType.IMAGE : FieldContentType.STRING);
                                 field.setTitle(col.title);
@@ -623,7 +627,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             console.log(e);
         }
         return true;
-    }
+    };
 
     renderGPTTextCall = async (template: Template, col: Col, fieldNum: number): Promise<boolean> => {
         const wordLimit = (size: TemplateFieldSize) => {
@@ -667,7 +671,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         }
 
         return true;
-    }
+    };
 
     createDocsFromTemplate = async (template: Template) => {
         const dv = this._dataViz;
@@ -676,61 +680,72 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         this._docsRendering = true;
 
-        const fields: string[] = Array.from(Object.keys(dv.records[0]));  
+        const fields: string[] = Array.from(Object.keys(dv.records[0]));
         const selectedRows = NumListCast(dv.layoutDoc.dataViz_selectedRows);
-    
+
         const rowContents: { [title: string]: string }[] = selectedRows.map(row => {
             const values: { [title: string]: string } = {};
             fields.forEach(col => {
                 values[col] = dv.records[row][col];
             });
-    
+
             return values;
         });
-    
-        const processContent = async (content: {[title: string]: string}) => {
-            const templateCopy = template.cloneBase(); 
-
-            fields.filter(title => title).forEach(title => {
-                const field = templateCopy.getFieldByTitle(title);
-                if (field === undefined) { return };
-                field.setContent(content[title]);
-            });
-    
-            const gptPromises = this._userCreatedFields.filter(field => field.type === TemplateFieldType.TEXT).map(field => {
-                const title = field.title;
-                const templateField = templateCopy.getFieldByTitle(title);
-                if (templateField === undefined) { return };
-                const templatefieldID = templateField.getID;
-    
-                return this.renderGPTTextCall(templateCopy, field, templatefieldID);
-            });
 
-            const imagePromises = this._userCreatedFields.filter(field => field.type === TemplateFieldType.VISUAL).map(field => {
-                const title = field.title;
-                const templateField = templateCopy.getFieldByTitle(title);
-                if (templateField === undefined) { return };
-                const templatefieldID = templateField.getID;
-    
-                return this.renderGPTImageCall(templateCopy, field, templatefieldID);
-            });
-    
+        const processContent = async (content: { [title: string]: string }) => {
+            const templateCopy = template.cloneBase();
+
+            fields
+                .filter(title => title)
+                .forEach(title => {
+                    const field = templateCopy.getFieldByTitle(title);
+                    if (field === undefined) {
+                        return;
+                    }
+                    field.setContent(content[title]);
+                });
+
+            const gptPromises = this._userCreatedFields
+                .filter(field => field.type === TemplateFieldType.TEXT)
+                .map(field => {
+                    const title = field.title;
+                    const templateField = templateCopy.getFieldByTitle(title);
+                    if (templateField === undefined) {
+                        return;
+                    }
+                    const templatefieldID = templateField.getID;
+
+                    return this.renderGPTTextCall(templateCopy, field, templatefieldID);
+                });
+
+            const imagePromises = this._userCreatedFields
+                .filter(field => field.type === TemplateFieldType.VISUAL)
+                .map(field => {
+                    const title = field.title;
+                    const templateField = templateCopy.getFieldByTitle(title);
+                    if (templateField === undefined) {
+                        return;
+                    }
+                    const templatefieldID = templateField.getID;
+
+                    return this.renderGPTImageCall(templateCopy, field, templatefieldID);
+                });
+
             await Promise.all(gptPromises);
 
             await Promise.all(imagePromises);
-    
+
             return templateCopy.getRenderedDoc();
         };
-    
+
         const promises = rowContents.map(content => processContent(content));
-    
+
         const renderedDocs = await Promise.all(promises);
 
         this._docsRendering = false;
 
         return renderedDocs;
-    }
-    
+    };
 
     addRenderedCollectionToMainview = () => {
         const collection = this._renderedDocCollection;
@@ -740,7 +755,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         collection.y = this._pageY - this._menuDimensions.height;
         mainCollection.addDocument(collection);
         this.closeMenu();
-    }
+    };
 
     @action setExpandedView = (template: Template | undefined) => {
         if (template) {
@@ -752,8 +767,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         }
     };
 
-    get editingWindow(){
-        const rendered = !this._expandedPreview ? null :
+    get editingWindow() {
+        const rendered = !this._expandedPreview ? null : (
             <div className="docCreatorMenu-expanded-template-preview">
                 <DocumentView
                     Document={this._expandedPreview}
@@ -763,7 +778,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     removeDocument={returnFalse}
                     PanelWidth={() => this._menuDimensions.width - 10}
                     PanelHeight={() => this._menuDimensions.height - 60}
-                    ScreenToLocalTransform={() => new Transform(-this._pageX - 5,-this._pageY - 35, 1)}
+                    ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)}
                     renderDepth={5}
                     whenChildContentsActiveChanged={emptyFunction}
                     focus={emptyFunction}
@@ -777,19 +792,31 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     fitWidth={returnFalse}
                 />
             </div>
+        );
 
         return (
             <div className="docCreatorMenu-expanded-template-preview">
-                <div className="top-panel"/>
+                <div className="top-panel" />
                 {rendered}
                 <div className="right-buttons-panel">
-                    <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this.setUpButtonClick(e, () => {
-                        this._currEditingTemplate && this.updateTemplatePreview(this._currEditingTemplate); 
-                        this.setExpandedView(undefined)}
-                        )}>
+                    <button
+                        className="docCreatorMenu-menu-button section-reveal-options top-right"
+                        onPointerDown={e =>
+                            this.setUpButtonClick(e, () => {
+                                this._currEditingTemplate && this.updateTemplatePreview(this._currEditingTemplate);
+                                this.setExpandedView(undefined);
+                            })
+                        }>
                         <FontAwesomeIcon icon="minimize" />
                     </button>
-                    <button className="docCreatorMenu-menu-button section-reveal-options top-right-lower" onPointerDown={e => this.setUpButtonClick(e, () => {this._currEditingTemplate?.resetToBase(); this.setExpandedView(this._currEditingTemplate);})}>
+                    <button
+                        className="docCreatorMenu-menu-button section-reveal-options top-right-lower"
+                        onPointerDown={e =>
+                            this.setUpButtonClick(e, () => {
+                                this._currEditingTemplate?.resetToBase();
+                                this.setExpandedView(this._currEditingTemplate);
+                            })
+                        }>
                         <FontAwesomeIcon icon="arrows-rotate" color="white" />
                     </button>
                 </div>
@@ -798,7 +825,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     }
 
     get templatesPreviewContents() {
-
         const GPTOptions = <div></div>;
 
         return (
@@ -820,80 +846,10 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                         <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
                                     </div>
                                 ) : (
-                                    this._suggestedTemplatePreviews
-                                        .map(({doc, template}) => (
-                                            <div
-                                                className="docCreatorMenu-preview-window"
-                                                key='0'
-                                                style={{
-                                                    border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
-                                                    boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
-                                                }}
-                                                onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(template)))}>
-                                                <button
-                                                    className="option-button left"
-                                                    onPointerDown={e =>
-                                                        this.setUpButtonClick(e, () => {
-                                                            this.setExpandedView(template);
-                                                        })
-                                                    }>
-                                                    <FontAwesomeIcon icon="magnifying-glass" color="white" />
-                                                </button>
-                                                <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.addUserTemplate(template))}>
-                                                    <FontAwesomeIcon icon="plus" color="white" />
-                                                </button>
-                                                <DocumentView
-                                                    Document={doc}
-                                                    isContentActive={emptyFunction} // !!! should be return false
-                                                    addDocument={returnFalse}
-                                                    moveDocument={returnFalse}
-                                                    removeDocument={returnFalse}
-                                                    PanelWidth={() => this._selectedTemplate === template ? 104 : 111}
-                                                    PanelHeight={() => this._selectedTemplate === template ? 104 : 111}
-                                                    ScreenToLocalTransform={() => new Transform(-this._pageX - 5,-this._pageY - 35, 1)}
-                                                    renderDepth={1}
-                                                    whenChildContentsActiveChanged={emptyFunction}
-                                                    focus={emptyFunction}
-                                                    styleProvider={DefaultStyleProvider}
-                                                    addDocTab={this._props.addDocTab}
-                                                    pinToPres={() => undefined}
-                                                    childFilters={returnEmptyFilter}
-                                                    childFiltersByRanges={returnEmptyFilter}
-                                                    searchFilterDocs={returnEmptyDoclist}
-                                                    fitContentsToBox={returnFalse}
-                                                    fitWidth={returnFalse}
-                                                    hideDecorations={true}
-                                                    />
-                                            </div>
-                                        ))
-                                )}
-                            </div>
-                            <div className="docCreatorMenu-GPT-options">
-                                <div className="docCreatorMenu-GPT-options-container">
-                                    <button className="docCreatorMenu-menu-button" onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}>
-                                        <FontAwesomeIcon icon="arrows-rotate" />
-                                    </button>
-                                </div>
-                                {this._GPTOpt ? GPTOptions : null}
-                            </div>
-                        </div>
-                        <hr className="docCreatorMenu-option-divider full no-margin" />
-                        <div className="docCreatorMenu-section">
-                            <div className="docCreatorMenu-section-topbar">
-                                <div className="docCreatorMenu-section-title">Your Templates</div>
-                                <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => (this._GPTOpt = !this._GPTOpt))}>
-                                    <FontAwesomeIcon icon="gear" />
-                                </button>
-                            </div>
-                            <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._menuDimensions.width > 400 ? 'center' : '' }}>
-                                <div className="docCreatorMenu-preview-window empty">
-                                    <FontAwesomeIcon icon="plus" color="rgb(160, 160, 160)" />
-                                </div>
-                                {this._userTemplates
-                                    .map(({template, doc}) => (
+                                    this._suggestedTemplatePreviews.map(({ doc, template }) => (
                                         <div
                                             className="docCreatorMenu-preview-window"
-                                            key='0'
+                                            key="0"
                                             style={{
                                                 border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
                                                 boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
@@ -908,8 +864,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                                 }>
                                                 <FontAwesomeIcon icon="magnifying-glass" color="white" />
                                             </button>
-                                            <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.removeUserTemplate(template))}>
-                                                <FontAwesomeIcon icon="minus" color="white" />
+                                            <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.addUserTemplate(template))}>
+                                                <FontAwesomeIcon icon="plus" color="white" />
                                             </button>
                                             <DocumentView
                                                 Document={doc}
@@ -917,8 +873,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                                 addDocument={returnFalse}
                                                 moveDocument={returnFalse}
                                                 removeDocument={returnFalse}
-                                                PanelWidth={() => this._selectedTemplate === template ? 104 : 111}
-                                                PanelHeight={() => this._selectedTemplate === template ? 104 : 111}
+                                                PanelWidth={() => (this._selectedTemplate === template ? 104 : 111)}
+                                                PanelHeight={() => (this._selectedTemplate === template ? 104 : 111)}
                                                 ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)}
                                                 renderDepth={1}
                                                 whenChildContentsActiveChanged={emptyFunction}
@@ -932,9 +888,77 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                                 fitContentsToBox={returnFalse}
                                                 fitWidth={returnFalse}
                                                 hideDecorations={true}
-                                                />
+                                            />
                                         </div>
-                                    ))}
+                                    ))
+                                )}
+                            </div>
+                            <div className="docCreatorMenu-GPT-options">
+                                <div className="docCreatorMenu-GPT-options-container">
+                                    <button className="docCreatorMenu-menu-button" onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}>
+                                        <FontAwesomeIcon icon="arrows-rotate" />
+                                    </button>
+                                </div>
+                                {this._GPTOpt ? GPTOptions : null}
+                            </div>
+                        </div>
+                        <hr className="docCreatorMenu-option-divider full no-margin" />
+                        <div className="docCreatorMenu-section">
+                            <div className="docCreatorMenu-section-topbar">
+                                <div className="docCreatorMenu-section-title">Your Templates</div>
+                                <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => (this._GPTOpt = !this._GPTOpt))}>
+                                    <FontAwesomeIcon icon="gear" />
+                                </button>
+                            </div>
+                            <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._menuDimensions.width > 400 ? 'center' : '' }}>
+                                <div className="docCreatorMenu-preview-window empty">
+                                    <FontAwesomeIcon icon="plus" color="rgb(160, 160, 160)" />
+                                </div>
+                                {this._userTemplates.map(({ template, doc }) => (
+                                    <div
+                                        className="docCreatorMenu-preview-window"
+                                        key="0"
+                                        style={{
+                                            border: this._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
+                                            boxShadow: this._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
+                                        }}
+                                        onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(template)))}>
+                                        <button
+                                            className="option-button left"
+                                            onPointerDown={e =>
+                                                this.setUpButtonClick(e, () => {
+                                                    this.setExpandedView(template);
+                                                })
+                                            }>
+                                            <FontAwesomeIcon icon="magnifying-glass" color="white" />
+                                        </button>
+                                        <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this.removeUserTemplate(template))}>
+                                            <FontAwesomeIcon icon="minus" color="white" />
+                                        </button>
+                                        <DocumentView
+                                            Document={doc}
+                                            isContentActive={emptyFunction} // !!! should be return false
+                                            addDocument={returnFalse}
+                                            moveDocument={returnFalse}
+                                            removeDocument={returnFalse}
+                                            PanelWidth={() => (this._selectedTemplate === template ? 104 : 111)}
+                                            PanelHeight={() => (this._selectedTemplate === template ? 104 : 111)}
+                                            ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)}
+                                            renderDepth={1}
+                                            whenChildContentsActiveChanged={emptyFunction}
+                                            focus={emptyFunction}
+                                            styleProvider={DefaultStyleProvider}
+                                            addDocTab={this._props.addDocTab}
+                                            pinToPres={() => undefined}
+                                            childFilters={returnEmptyFilter}
+                                            childFiltersByRanges={returnEmptyFilter}
+                                            searchFilterDocs={returnEmptyDoclist}
+                                            fitContentsToBox={returnFalse}
+                                            fitWidth={returnFalse}
+                                            hideDecorations={true}
+                                        />
+                                    </div>
+                                ))}
                             </div>
                         </div>
                     </div>
@@ -943,7 +967,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         );
     }
 
-
     @action updateXMargin = (input: string) => {
         this._layout.xMargin = Number(input);
         setTimeout(() => {
@@ -989,8 +1012,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         }
     }
 
-    screenToLocalTransform = () => this._props.ScreenToLocalTransform();
-
     applyLayout = (collection: Doc, docs: Doc[]) => {
         const { horizontalSpan, verticalSpan } = this.previewInfo;
         collection._height = verticalSpan;
@@ -1001,12 +1022,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         const xGap: number = layout.xMargin;
         const yGap: number = layout.yMargin;
         // const repeat: number = templateInfo.layout.repeat;
-        const startX: number = -Number(collection._width)/2;
-        const startY: number = -Number(collection._height)/2;
+        const startX: number = -Number(collection._width) / 2;
+        const startY: number = -Number(collection._height) / 2;
         const docHeight: number = Number(docs[0]._height);
         const docWidth: number = Number(docs[0]._width);
 
-        if (columns === 0 || docs.length === 0){
+        if (columns === 0 || docs.length === 0) {
             return;
         }
 
@@ -1030,16 +1051,16 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     };
 
     @computed
-    get previewInfo(){
+    get previewInfo() {
         const docHeight: number = Number(this._fullyRenderedDocs[0]._height);
         const docWidth: number = Number(this._fullyRenderedDocs[0]._width);
         const layout = this._layout;
         return {
             docHeight: docHeight,
             docWidth: docWidth,
-            horizontalSpan: (docWidth + layout.xMargin) * (this.columnsCount) - layout.xMargin,
-            verticalSpan: (docHeight + layout.yMargin) * (this.rowsCount) - layout.yMargin,
-        }
+            horizontalSpan: (docWidth + layout.xMargin) * this.columnsCount - layout.xMargin,
+            verticalSpan: (docHeight + layout.yMargin) * this.rowsCount - layout.yMargin,
+        };
     }
 
     /**
@@ -1051,7 +1072,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         const { horizontalSpan, verticalSpan } = this.previewInfo;
 
-        const collectionFactory = (): (docs: Doc[], options: DocumentOptions) => Doc => {
+        const collectionFactory = (): ((docs: Doc[], options: DocumentOptions) => Doc) => {
             switch (this._layout.type) {
                 case LayoutType.CAROUSEL3D:
                     return Docs.Create.Carousel3DDocument;
@@ -1066,7 +1087,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                 default:
                     return Docs.Create.FreeformDocument;
             }
-        }
+        };
 
         const collection: Doc = collectionFactory()(this._fullyRenderedDocs, {
             isDefaultTemplateDoc: true,
@@ -1079,17 +1100,16 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         this.applyLayout(collection, this._fullyRenderedDocs);
 
         this._renderedDocCollection = collection;
-    }
+    };
 
     layoutPreviewContents = () => {
-
         return this._docsRendering ? (
             <div className="docCreatorMenu-layout-preview-window-wrapper loading">
                 <div className="loading-spinner">
                     <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
-                </div> 
+                </div>
             </div>
-            ) : !this._renderedDocCollection? null : (
+        ) : !this._renderedDocCollection ? null : (
             <div className="docCreatorMenu-layout-preview-window-wrapper">
                 <DocumentView
                     Document={this._renderedDocCollection}
@@ -1099,7 +1119,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     removeDocument={returnFalse}
                     PanelWidth={() => this._menuDimensions.width - 80}
                     PanelHeight={() => this._menuDimensions.height - 105}
-                    ScreenToLocalTransform={() => new Transform(-this._pageX - 5,-this._pageY - 35, 1)}
+                    ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)}
                     renderDepth={5}
                     whenChildContentsActiveChanged={emptyFunction}
                     focus={emptyFunction}
@@ -1114,7 +1134,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     hideDecorations={true}
                 />
             </div>
-        )
+        );
     };
 
     get optionsMenuContents() {
-- 
cgit v1.2.3-70-g09d2

