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/nodes/imageEditor/ImageEditor.tsx | 758 +++++++++++++++++++++
 1 file changed, 758 insertions(+)
 create mode 100644 src/client/views/nodes/imageEditor/ImageEditor.tsx

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

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;
-- 
cgit v1.2.3-70-g09d2


From d0569fcb79389b3c4f3d60e2a84f6f9220ca9cf3 Mon Sep 17 00:00:00 2001
From: eleanor-park <113556828+eleanor-park@users.noreply.github.com>
Date: Sun, 20 Oct 2024 23:52:20 -0400
Subject: added different cut types, haven't been able to run & test

---
 .../views/nodes/imageEditor/ImageEditor.scss       |  6 +++
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 56 ++++++++++++++++++++--
 .../imageEditorUtils/imageEditorInterfaces.ts      |  6 ++-
 3 files changed, 61 insertions(+), 7 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss
index 21c28f6da..49146aa23 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.scss
+++ b/src/client/views/nodes/imageEditor/ImageEditor.scss
@@ -89,6 +89,12 @@ $scale: 0.5;
                 gap: 10px;
             }
 
+            .cutToolsContainer {
+                display: grid;
+                gap: 5px;
+                grid-template-columns: 1fr 1fr;
+            }
+
             .undoRedoContainer {
                 justify-content: center;
                 display: flex;
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 86f7d8d29..d9f46876e 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -25,13 +25,14 @@ 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 { activeColor, bgColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants';
+import { BrushMode, 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';
+import { SettingsManager } from '../../../util/SettingsManager';
 
 interface GenerativeFillProps {
     imageEditorOpen: boolean;
@@ -57,13 +58,13 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
     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,
     });
+    const [cutType, setCutType] = useState<BrushMode>(BrushMode.IN);
     // whether to create a new collection or not
     const [isNewCollection, setIsNewCollection] = useState(true);
     // the current image in the main canvas
@@ -84,6 +85,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
     // constants for image cutting
     const cutPts = useRef<Point[]>([]);
 
+
     /**
      *
      * @param type The new tool type we are changing to
@@ -380,8 +382,21 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                 maxY = Math.max(cutPts.current[i].y, maxY);
             }
             ctx.closePath();
-            ctx.globalCompositeOperation = 'destination-in';
-            ctx.fill();
+            switch (cutType) { // may need to move this before the drawing at all for the line cases
+                case BrushMode.IN:
+                    ctx.globalCompositeOperation = 'destination-in';
+                    ctx.fill();
+                    break;
+                case BrushMode.OUT:
+                    ctx.globalCompositeOperation = 'destination-out';
+                    ctx.fill();
+                    break;
+                case BrushMode.LINE_OUT:
+                    ctx.globalCompositeOperation = 'destination-out';
+                    break;
+                case BrushMode.LINE_IN:
+                    ctx.globalCompositeOperation = 'destination-in';
+            }
         }
 
         const url = canvas.toDataURL(); // this does the same thing as convert img to canvasurl
@@ -578,6 +593,37 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                         return ImageToolButton(tool, tool.type === currTool.type, changeTool);
                     })}
                 </div>
+                {currTool.type == ImageToolType.Cut && 
+                    <div className="cutToolContainer"> 
+                        <Button 
+                            text="Keep in"
+                            type={Type.TERT}
+                            color={cutType == BrushMode.IN ? SettingsManager.userVariantColor : bgColor}
+                            onClick={() => {
+                                setCutType(BrushMode.IN);
+                            }}/>
+                        <Button 
+                            text="Keep out"
+                            type={Type.TERT}
+                            color={cutType == BrushMode.OUT ? SettingsManager.userVariantColor : bgColor}
+                            onClick={() => {
+                                setCutType(BrushMode.OUT);
+                            }}/>
+                        <Button 
+                            text="Draw in"
+                            type={Type.TERT}
+                            color={cutType == BrushMode.LINE_IN ? SettingsManager.userVariantColor : bgColor}
+                            onClick={() => {
+                                setCutType(BrushMode.LINE_IN);
+                            }}/>
+                        <Button 
+                            text="Erase"
+                            type={Type.TERT}
+                            color={cutType == BrushMode.LINE_OUT ? SettingsManager.userVariantColor : bgColor}
+                            onClick={() => {
+                                setCutType(BrushMode.LINE_OUT);
+                            }}/>
+                    </div>}
                 <div className="sliderContainer" onPointerDown={e => e.stopPropagation()}>
                     {currTool.type === ImageToolType.GenerativeFill && (
                         <Slider
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
index f4ae7d9c4..bd2fac775 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
@@ -28,8 +28,10 @@ export interface ImageEditTool {
 }
 
 export enum BrushMode {
-    ADD,
-    SUBTRACT,
+    IN,
+    OUT,
+    LINE_IN,
+    LINE_OUT,
 }
 
 export interface ImageDimensions {
-- 
cgit v1.2.3-70-g09d2


From e8661840c39219fd848e664c990943450c41bbee Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Mon, 28 Oct 2024 23:10:24 -0400
Subject: basic structure of 4 cut modes implemented, many bugs to fix

---
 .../views/nodes/imageEditor/ImageEditor.scss       |  59 +++--
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 265 +++++++++++----------
 .../views/nodes/imageEditor/ImageEditorButtons.tsx |   2 +-
 .../imageEditorUtils/imageEditorInterfaces.ts      |   3 +-
 4 files changed, 180 insertions(+), 149 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss
index 49146aa23..c691e6a18 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.scss
+++ b/src/client/views/nodes/imageEditor/ImageEditor.scss
@@ -75,38 +75,47 @@ $scale: 0.5;
             }
         }
 
-        .iconContainer {
+        .sideControlsContainer {
+            width: 160px;
             position: absolute;
-            top: 3rem;
-            left: 2rem;
-            display: flex;
-            flex-direction: column;
-            gap: 4rem;
+            left: 0;
+            height: 100%;
 
-            .imageToolsContainer {
+            .sideControls {
+                position: absolute;
+                width: 120px;
+                top: 3rem;
+                left: 2rem;
                 display: flex;
                 flex-direction: column;
-                gap: 10px;
-            }
+                gap: 1rem;
 
-            .cutToolsContainer {
-                display: grid;
-                gap: 5px;
-                grid-template-columns: 1fr 1fr;
-            }
+                .imageToolsContainer {
+                    display: flex;
+                    flex-direction: column;
+                    gap: 10px;
+                }
 
-            .undoRedoContainer {
-                justify-content: center;
-                display: flex;
-                flex-direction: row;
-            }
+                .cutToolsContainer {
+                    display: grid;
+                    gap: 5px;
+                    grid-template-columns: 1fr 1fr;
+                }
 
-            .sliderContainer {
-                height: 225px;
-                width: 100%;
-                display: flex;
-                justify-content: center;
-                cursor: pointer;
+                .undoRedoContainer {
+                    justify-content: center;
+                    display: flex;
+                    flex-direction: row;
+                }
+
+                .sliderContainer {
+                    margin: 3rem 0;
+                    height: 225px;
+                    width: 100%;
+                    display: flex;
+                    justify-content: center;
+                    cursor: pointer;
+                }
             }
         }
 
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index d9f46876e..980f3e566 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -4,7 +4,7 @@
 /* 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 { Button, IconButton, Type } from 'browndash-components';
 import * as React from 'react';
 import { useEffect, useRef, useState } from 'react';
 import { CgClose } from 'react-icons/cg';
@@ -85,7 +85,6 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
     // constants for image cutting
     const cutPts = useRef<Point[]>([]);
 
-
     /**
      *
      * @param type The new tool type we are changing to
@@ -356,7 +355,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         setLoading(false);
     };
 
-    const cutImage = async () => {
+    const cutImage = async (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[]) => {
         const img = currImg.current;
         const canvas = canvasRef.current;
         if (!canvas || !img) return;
@@ -381,21 +380,22 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                 maxX = Math.max(cutPts.current[i].x, maxX);
                 maxY = Math.max(cutPts.current[i].y, maxY);
             }
-            ctx.closePath();
-            switch (cutType) { // may need to move this before the drawing at all for the line cases
+            switch (currCutType) {
                 case BrushMode.IN:
+                    ctx.closePath();
                     ctx.globalCompositeOperation = 'destination-in';
                     ctx.fill();
                     break;
                 case BrushMode.OUT:
+                    ctx.closePath();
                     ctx.globalCompositeOperation = 'destination-out';
                     ctx.fill();
                     break;
-                case BrushMode.LINE_OUT:
-                    ctx.globalCompositeOperation = 'destination-out';
-                    break;
                 case BrushMode.LINE_IN:
                     ctx.globalCompositeOperation = 'destination-in';
+                    ctx.lineWidth = brushWidth + 20;
+                    ctx.stroke();
+                    break;
             }
         }
 
@@ -423,31 +423,41 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         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;
+            let finalImg: HTMLImageElement | undefined = image;
+            let finalImgURL: string = url;
+            if (currCutType == BrushMode.IN) {
+                const croppedData = cropImage(image, minX, maxY, minY, maxY);
+                finalImg = croppedData;
+                finalImgURL = croppedData.src;
+            }
+            currImg.current = finalImg;
+            const newImgDoc = await createNewImgDoc(finalImg, isFirstDoc.current);
             if (newImgDoc) {
                 const docData = newImgDoc[DocData];
                 docData.backgroundColor = 'transparent';
+                if (isFirstDoc.current) isFirstDoc.current = false;
+                setEdits([...prevEdits, { url: finalImgURL, saveRes: undefined }]);
             }
-            setEdits(prevEdits => [...prevEdits, { url: croppedURL, saveRes: undefined }]);
             setLoading(false);
             cutPts.current.length = 0;
         };
     };
 
+    const cropImage = (image: HTMLImageElement, minX: number, maxX: number, minY: number, maxY: number) => {
+        const croppedCanvas = document.createElement('canvas');
+        const croppedCtx = croppedCanvas.getContext('2d');
+        if (!croppedCtx) return image;
+        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;
+        return croppedImage;
+    };
+
     // adjusts all the img positions to be aligned
     const adjustImgPositions = () => {
         if (!parentDoc.current) return;
@@ -577,7 +587,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                         labelPlacement="end"
                         sx={{ whiteSpace: 'nowrap' }}
                     />
-                    <ApplyFuncButtons onClick={currTool.applyFunc} loading={loading} onReset={handleReset} btnText={currTool.btnText} />
+                    <ApplyFuncButtons onClick={() => currTool.applyFunc(cutType, cursorData.width, edits)} loading={loading} onReset={handleReset} btnText={currTool.btnText} />
                     <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
                 </div>
             </div>
@@ -587,109 +597,120 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
     // 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>
-                {currTool.type == ImageToolType.Cut && 
-                    <div className="cutToolContainer"> 
-                        <Button 
-                            text="Keep in"
-                            type={Type.TERT}
-                            color={cutType == BrushMode.IN ? SettingsManager.userVariantColor : bgColor}
-                            onClick={() => {
-                                setCutType(BrushMode.IN);
-                            }}/>
-                        <Button 
-                            text="Keep out"
-                            type={Type.TERT}
-                            color={cutType == BrushMode.OUT ? SettingsManager.userVariantColor : bgColor}
-                            onClick={() => {
-                                setCutType(BrushMode.OUT);
-                            }}/>
-                        <Button 
-                            text="Draw in"
-                            type={Type.TERT}
-                            color={cutType == BrushMode.LINE_IN ? SettingsManager.userVariantColor : bgColor}
-                            onClick={() => {
-                                setCutType(BrushMode.LINE_IN);
-                            }}/>
-                        <Button 
-                            text="Erase"
-                            type={Type.TERT}
-                            color={cutType == BrushMode.LINE_OUT ? SettingsManager.userVariantColor : bgColor}
-                            onClick={() => {
-                                setCutType(BrushMode.LINE_OUT);
-                            }}/>
-                    </div>}
-                <div className="sliderContainer" onPointerDown={e => e.stopPropagation()}>
-                    {currTool.type === ImageToolType.GenerativeFill && (
-                        <Slider
-                            sx={{
-                                '& input[type="range"]': {
-                                    WebkitAppearance: 'slider-vertical',
-                                },
+            <div className="sideControlsContainer" style={{ backgroundColor: bgColor }}>
+                <div className="sideControls">
+                    <div className="imageToolsContainer">
+                        {imageEditTools.map(tool => {
+                            return ImageToolButton(tool, tool.type === currTool.type, changeTool);
+                        })}
+                    </div>
+                    {currTool.type == ImageToolType.Cut && (
+                        <div className="cutToolsContainer">
+                            <Button
+                                style={{ width: '100%' }}
+                                text="Keep in"
+                                type={Type.TERT}
+                                color={cutType == BrushMode.IN ? SettingsManager.userColor : bgColor}
+                                onClick={() => {
+                                    setCutType(BrushMode.IN);
+                                }}
+                            />
+                            <Button
+                                style={{ width: '100%' }}
+                                text="Keep out"
+                                type={Type.TERT}
+                                color={cutType == BrushMode.OUT ? SettingsManager.userColor : bgColor}
+                                onClick={() => {
+                                    setCutType(BrushMode.OUT);
+                                }}
+                            />
+                            <Button
+                                style={{ width: '100%' }}
+                                text="Draw in"
+                                type={Type.TERT}
+                                color={cutType == BrushMode.LINE_IN ? SettingsManager.userColor : bgColor}
+                                onClick={() => {
+                                    setCutType(BrushMode.LINE_IN);
+                                }}
+                            />
+                            <Button
+                                style={{ width: '100%' }}
+                                text="Erase"
+                                type={Type.TERT}
+                                color={cutType == BrushMode.LINE_OUT ? SettingsManager.userColor : bgColor}
+                                onClick={() => {
+                                    setCutType(BrushMode.LINE_OUT);
+                                }}
+                            />
+                        </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();
                             }}
-                            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 }));
+                            onPointerUp={e => {
+                                e.stopPropagation();
                             }}
+                            color={activeColor}
+                            tooltip="Undo"
+                            icon={<IoMdUndo />}
                         />
-                    )}
-                    {currTool.type === ImageToolType.Cut && (
-                        <Slider
-                            sx={{
-                                '& input[type="range"]': {
-                                    WebkitAppearance: 'slider-vertical',
-                                },
+                        <IconButton
+                            style={{ cursor: 'pointer' }}
+                            onPointerDown={e => {
+                                e.stopPropagation();
+                                handleRedo();
                             }}
-                            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 }));
+                            onPointerUp={e => {
+                                e.stopPropagation();
                             }}
+                            color={activeColor}
+                            tooltip="Redo"
+                            icon={<IoMdRedo />}
                         />
-                    )}
-                </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>
             </div>
         );
diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
index e90babf9b..cb963616b 100644
--- a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
@@ -55,7 +55,7 @@ export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTo
     return (
         <div className="imageEditorButtonContainer">
             <Button
-                style={{ width: '100%', border: '1px' }}
+                style={{ width: '100%' }}
                 text={tool.name}
                 type={Type.TERT}
                 color={isActive ? SettingsManager.userVariantColor : bgColor}
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
index bd2fac775..660af7c42 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
@@ -1,4 +1,5 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Doc } from '../../../../../fields/Doc';
 
 export interface CursorData {
     x: number;
@@ -21,7 +22,7 @@ export interface ImageEditTool {
     name: string;
     btnText: string;
     icon: IconProp;
-    applyFunc: () => Promise<void>;
+    applyFunc: (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[]) => Promise<void>;
     sliderMin?: number;
     sliderMax?: number;
     sliderDefault?: number;
-- 
cgit v1.2.3-70-g09d2


From 4dbfe98a13b441a18b37c6bdcc6b62dab3b05fee Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Wed, 30 Oct 2024 00:27:20 -0400
Subject: bug fixes to cut modes and rendering

---
 src/client/views/nodes/imageEditor/ImageEditor.tsx     | 18 ++++++++++--------
 .../imageEditorUtils/imageEditorInterfaces.ts          |  2 +-
 2 files changed, 11 insertions(+), 9 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 980f3e566..e6d651ee9 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -57,7 +57,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
     // 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 [isFirstDoc, setIsFirstDoc] = useState<boolean>(true);
     const [input, setInput] = useState('');
     const [loading, setLoading] = useState(false);
     const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
@@ -355,7 +355,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         setLoading(false);
     };
 
-    const cutImage = async (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[]) => {
+    const cutImage = async (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], firstDoc: boolean) => {
         const img = currImg.current;
         const canvas = canvasRef.current;
         if (!canvas || !img) return;
@@ -366,13 +366,14 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         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!
+        if (currCutType !== BrushMode.LINE_OUT) handleReset(); // gets rid of the visible brush strokes (mostly needed for line_in) unless it's erasing (which depends on the brush strokes)
         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
+            ctx.moveTo(cutPts.current[0].x, cutPts.current[0].y);
             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);
@@ -393,7 +394,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                     break;
                 case BrushMode.LINE_IN:
                     ctx.globalCompositeOperation = 'destination-in';
-                    ctx.lineWidth = brushWidth + 20;
+                    ctx.lineWidth = brushWidth + 10; // added offset because width gets cut off a little bit
                     ctx.stroke();
                     break;
             }
@@ -426,16 +427,16 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             let finalImg: HTMLImageElement | undefined = image;
             let finalImgURL: string = url;
             if (currCutType == BrushMode.IN) {
-                const croppedData = cropImage(image, minX, maxY, minY, maxY);
+                const croppedData = cropImage(image, minX, maxX, minY, maxY);
                 finalImg = croppedData;
                 finalImgURL = croppedData.src;
             }
             currImg.current = finalImg;
-            const newImgDoc = await createNewImgDoc(finalImg, isFirstDoc.current);
+            const newImgDoc = await createNewImgDoc(finalImg, firstDoc);
             if (newImgDoc) {
                 const docData = newImgDoc[DocData];
                 docData.backgroundColor = 'transparent';
-                if (isFirstDoc.current) isFirstDoc.current = false;
+                if (firstDoc) setIsFirstDoc(false);
                 setEdits([...prevEdits, { url: finalImgURL, saveRes: undefined }]);
             }
             setLoading(false);
@@ -557,6 +558,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             DocumentView.addViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce());
         }
         setEdits([]);
+        setIsFirstDoc(true);
     };
 
     // defines the tools and sets current tool
@@ -587,7 +589,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                         labelPlacement="end"
                         sx={{ whiteSpace: 'nowrap' }}
                     />
-                    <ApplyFuncButtons onClick={() => currTool.applyFunc(cutType, cursorData.width, edits)} loading={loading} onReset={handleReset} btnText={currTool.btnText} />
+                    <ApplyFuncButtons onClick={() => currTool.applyFunc(cutType, cursorData.width, edits, isFirstDoc)} loading={loading} onReset={handleReset} btnText={currTool.btnText} />
                     <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
                 </div>
             </div>
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
index 660af7c42..75659fc53 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
@@ -22,7 +22,7 @@ export interface ImageEditTool {
     name: string;
     btnText: string;
     icon: IconProp;
-    applyFunc: (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[]) => Promise<void>;
+    applyFunc: (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], isFirstDoc: boolean) => Promise<void>;
     sliderMin?: number;
     sliderMax?: number;
     sliderDefault?: number;
-- 
cgit v1.2.3-70-g09d2


From b5944e87f9d4f3149161de4de0d76db486461c76 Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Wed, 30 Oct 2024 19:38:18 -0400
Subject: integrated image stickers with palette

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/views/DocumentDecorations.tsx           |   4 +-
 src/client/views/LightboxView.tsx                  |   8 +-
 src/client/views/Main.tsx                          |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   8 +-
 src/client/views/nodes/ImageBox.tsx                |   8 +-
 src/client/views/nodes/imageEditor/ImageEditor.tsx |   2 +-
 src/client/views/smartdraw/AnnotationPalette.scss  |  56 ----
 src/client/views/smartdraw/AnnotationPalette.tsx   | 361 --------------------
 src/client/views/smartdraw/SmartDrawHandler.tsx    |   2 +-
 src/client/views/smartdraw/StickerPalette.scss     |  56 ++++
 src/client/views/smartdraw/StickerPalette.tsx      | 362 +++++++++++++++++++++
 src/fields/Doc.ts                                  |   2 +-
 14 files changed, 443 insertions(+), 436 deletions(-)
 delete mode 100644 src/client/views/smartdraw/AnnotationPalette.scss
 delete mode 100644 src/client/views/smartdraw/AnnotationPalette.tsx
 create mode 100644 src/client/views/smartdraw/StickerPalette.scss
 create mode 100644 src/client/views/smartdraw/StickerPalette.tsx

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 5f2a592ae..b79e8930b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1044,7 +1044,7 @@ export namespace Docs {
         }
 
         export function AnnoPaletteDocument(options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([Doc.MyAnnos]), { ...(options || {}) });
+            return InstanceFromProto(Prototypes.get(DocumentType.ANNOPALETTE), new List([Doc.MyStickers]), { ...(options || {}) });
         }
 
         export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 274bc79be..bb3d232fb 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -160,9 +160,9 @@ export class CurrentUserUtils {
         return DocUtils.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts);
     }
     
-    static setupAnnoPalette(doc: Doc, field="myAnnos") {
+    static setupAnnoPalette(doc: Doc, field="myStickers") {
         const reqdOpts:DocumentOptions = {
-            title: "Saved Annotations", _xMargin: 0, _layout_showTitle: "title", hidden: false, _chromeHidden: true,
+            title: "Stickers", _xMargin: 0, _layout_showTitle: "title", hidden: false, _chromeHidden: true,
             _dragOnlyWithinContainer: true, layout_hideContextMenu: true, isSystem: true, _forceActive: true,
             _layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
         };
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 62f2de776..24ebca751 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -232,9 +232,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                         if (iconViewDoc.activeFrame) {
                             iconViewDoc.opacity = 0; // if in an animation collection, set opacity to 0 to allow inkMasks and other documents to remain in the collection and to smoothly animate when they are activated in a different animation frame
                         } else {
-                            // if Doc is in the annotation palette, remove the flag indicating that it's saved
+                            // if Doc is in the sticker palette, remove the flag indicating that it's saved
                             const dragFactory = DocCast(iconView.Document.dragFactory);
-                            if (dragFactory && DocCast(dragFactory.cloneOf).savedAsAnno) DocCast(dragFactory.cloneOf).savedAsAnno = undefined;
+                            if (dragFactory && DocCast(dragFactory.cloneOf).savedAsSticker) DocCast(dragFactory.cloneOf).savedAsSticker = undefined;
 
                             // if this is a face Annotation doc, then just hide it.
                             if (iconView.Document.annotationOn && iconView.Document.face) iconView.Document.hidden = true;
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index a543b4875..f55ed2aa0 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -21,7 +21,7 @@ import { OverlayView } from './OverlayView';
 import { DefaultStyleProvider, returnEmptyDocViewList, wavyBorderPath } from './StyleProvider';
 import { DocumentView } from './nodes/DocumentView';
 import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
-import { AnnotationPalette } from './smartdraw/AnnotationPalette';
+import { StickerPalette } from './smartdraw/StickerPalette';
 
 interface LightboxViewProps {
     PanelWidth: number;
@@ -56,7 +56,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
     }[] = [];
     private _savedState: LightboxSavedState = {};
     private _history: { doc: Doc; target?: Doc }[] = [];
-    private _annoPaletteView: AnnotationPalette | null = null;
+    private _annoPaletteView: StickerPalette | null = null;
     @observable private _future: Doc[] = [];
     @observable private _layoutTemplate: Opt<Doc> = undefined;
     @observable private _layoutTemplateString: Opt<string> = undefined;
@@ -317,7 +317,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                     </GestureOverlay>
                 </div>
 
-                {this._showPalette && <AnnotationPalette ref={r => (this._annoPaletteView = r)} Document={DocCast(Doc.UserDoc().myLightboxDrawings)} />}
+                {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]),
@@ -331,7 +331,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
                 <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 annotation palette', this._showPalette === true, 'palette', '', this.togglePalette)}
+                {toggleBtn('lightboxView-paletteBtn', 'toggle sticker palette', this._showPalette === true, 'palette', '', this.togglePalette)}
                 {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Pen, 'pen', '', this.togglePen)}
                 {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', SnappingManager.ExploreMode, 'globe-americas', '', this.toggleExplore)}
             </div>
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 2a59f6dc3..dda543470 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -64,7 +64,7 @@ import { ImportElementBox } from './nodes/importBox/ImportElementBox';
 import { PresBox, PresElementBox } from './nodes/trails';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
 import { SearchBox } from './search/SearchBox';
-import { AnnotationPalette } from './smartdraw/AnnotationPalette';
+import { StickerPalette } from './smartdraw/StickerPalette';
 
 dotenv.config();
 
@@ -117,7 +117,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
         KeyValueBox.Init();
         PresBox.Init(TabDocView.AllTabDocs);
         DocumentContentsView.Init(KeyValueBox.LayoutString(), {
-            AnnotationPalette,
+            StickerPalette: StickerPalette,
             FormattedTextBox,
             ImageBox,
             FontIconBox,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d2bc8f2c2..8d73601d1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -42,7 +42,7 @@ import { FocusViewOptions } from '../../nodes/FocusViewOptions';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { OpenWhere, OpenWhereMod } from '../../nodes/OpenWhere';
 import { PinDocView, PinProps } from '../../PinFuncs';
-import { AnnotationPalette } from '../../smartdraw/AnnotationPalette';
+import { StickerPalette } from '../../smartdraw/StickerPalette';
 import { DrawingOptions, SmartDrawHandler } from '../../smartdraw/SmartDrawHandler';
 import { StyleProp } from '../../StyleProp';
 import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
@@ -1992,9 +1992,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             icon: 'pen-to-square',
         });
         optionItems.push({
-            description: this.Document.savedAsAnno ? 'Saved as Annotation!' : 'Save to Annotation Palette',
-            event: action(undoable(async () => await AnnotationPalette.addToPalette(this.Document), 'save to palette')),
-            icon: this.Document.savedAsAnno ? 'clipboard-check' : 'file-arrow-down',
+            description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
+            event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
+            icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down',
         });
         this._props.renderDepth &&
             optionItems.push({
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 0dfc0ec28..f98ff6d6e 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -32,6 +32,7 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { OverlayView } from '../OverlayView';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { PinDocView, PinProps } from '../PinFuncs';
+import { StickerPalette } from '../smartdraw/StickerPalette';
 import { StyleProp } from '../StyleProp';
 import { DocumentView } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
@@ -320,6 +321,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 }),
                 icon: 'pencil-alt',
             });
+            funcs.push({
+                description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
+                event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
+                icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down',
+            });
             ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
         }
     };
@@ -338,7 +344,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @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}
+        if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 };
         const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
         const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
         const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index e6d651ee9..9642dcc31 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -435,7 +435,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             const newImgDoc = await createNewImgDoc(finalImg, firstDoc);
             if (newImgDoc) {
                 const docData = newImgDoc[DocData];
-                docData.backgroundColor = 'transparent';
+                // docData.backgroundColor = 'transparent';
                 if (firstDoc) setIsFirstDoc(false);
                 setEdits([...prevEdits, { url: finalImgURL, saveRes: undefined }]);
             }
diff --git a/src/client/views/smartdraw/AnnotationPalette.scss b/src/client/views/smartdraw/AnnotationPalette.scss
deleted file mode 100644
index 4f11e8afc..000000000
--- a/src/client/views/smartdraw/AnnotationPalette.scss
+++ /dev/null
@@ -1,56 +0,0 @@
-.annotation-palette {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    position: absolute;
-    right: 14px;
-    top: 50px;
-    border-radius: 5px;
-    margin: auto;
-}
-
-.palette-create {
-    display: flex;
-    flex-direction: row;
-    width: 170px;
-
-    .palette-create-input {
-        color: black;
-        width: 170px;
-    }
-}
-
-.palette-create-options {
-    display: flex;
-    flex-direction: row;
-    justify-content: space-between;
-    width: 170px;
-    margin-top: 5px;
-
-    .palette-color {
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        width: 40px;
-    }
-
-    .palette-detail,
-    .palette-size {
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        width: 60px;
-    }
-}
-
-.palette-buttons {
-    width: 100%;
-    display: flex;
-    flex-direction: row;
-    justify-content: space-between;
-}
-
-.palette-save-reset {
-    display: flex;
-    flex-direction: row;
-}
diff --git a/src/client/views/smartdraw/AnnotationPalette.tsx b/src/client/views/smartdraw/AnnotationPalette.tsx
deleted file mode 100644
index dc0c01d36..000000000
--- a/src/client/views/smartdraw/AnnotationPalette.tsx
+++ /dev/null
@@ -1,361 +0,0 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Slider, Switch } from '@mui/material';
-import { Button } from 'browndash-components';
-import { action, makeObservable, observable } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { AiOutlineSend } from 'react-icons/ai';
-import ReactLoading from 'react-loading';
-import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
-import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
-import { ImageCast, NumCast } from '../../../fields/Types';
-import { ImageField } from '../../../fields/URLField';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { Docs } from '../../documents/Documents';
-import { makeUserTemplateButtonOrImage } from '../../util/DropConverter';
-import { SettingsManager } from '../../util/SettingsManager';
-import { Transform } from '../../util/Transform';
-import { undoBatch } from '../../util/UndoManager';
-import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider';
-import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
-import { FieldView } from '../nodes/FieldView';
-import './AnnotationPalette.scss';
-import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler';
-
-interface AnnotationPaletteProps {
-    Document: Doc;
-}
-
-/**
- * The AnnotationPalette can be toggled in the lightbox view of a document. The goal of the palette
- * is to offer an easy way for users to save then drag and drop repeated annotations onto a document.
- * These annotations can be of any annotation type and operate similarly to user templates.
- *
- * On the "add" side of the palette, there is a way to create a drawing annotation with GPT. Users can
- * enter the item to draw, toggle different settings, then GPT will generate three versions of the drawing
- * to choose from. These drawings can then be saved to the palette as annotations.
- */
-@observer
-export class AnnotationPalette extends ObservableReactComponent<AnnotationPaletteProps> {
-    @observable private _paletteMode: 'create' | 'view' = 'view';
-    @observable private _userInput: string = '';
-    @observable private _isLoading: boolean = false;
-    @observable private _canInteract: boolean = true;
-    @observable private _showRegenerate: boolean = false;
-    @observable private _docView: DocumentView | null = null;
-    @observable private _docCarouselView: DocumentView | null = null;
-    @observable private _opts: DrawingOptions = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
-    private _gptRes: string[] = [];
-
-    constructor(props: AnnotationPaletteProps) {
-        super(props);
-        makeObservable(this);
-    }
-
-    public static LayoutString(fieldKey: string) {
-        return FieldView.LayoutString(AnnotationPalette, fieldKey);
-    }
-
-    Contains = (view: DocumentView) => {
-        return (this._docView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docView)) || (this._docCarouselView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docCarouselView));
-    };
-
-    return170 = () => 170;
-
-    @action
-    handleKeyPress = async (event: React.KeyboardEvent) => {
-        if (event.key === 'Enter') {
-            await this.generateDrawings();
-        }
-    };
-
-    @action
-    setPaletteMode = (mode: 'create' | 'view') => {
-        this._paletteMode = mode;
-    };
-
-    @action
-    setUserInput = (input: string) => {
-        if (!this._isLoading) this._userInput = input;
-    };
-
-    @action
-    setDetail = (detail: number) => {
-        if (this._canInteract) this._opts.complexity = detail;
-    };
-
-    @action
-    setColor = (autoColor: boolean) => {
-        if (this._canInteract) this._opts.autoColor = autoColor;
-    };
-
-    @action
-    setSize = (size: number) => {
-        if (this._canInteract) this._opts.size = size;
-    };
-
-    @action
-    resetPalette = (changePaletteMode: boolean) => {
-        if (changePaletteMode) this.setPaletteMode('view');
-        this.setUserInput('');
-        this.setDetail(5);
-        this.setColor(true);
-        this.setSize(200);
-        this._showRegenerate = false;
-        this._canInteract = true;
-        this._opts = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
-        this._gptRes = [];
-        this._props.Document[DocData].data = undefined;
-    };
-
-    /**
-     * Adds a doc to the annotation palette. Gets a snapshot of the document to use as a preview in the palette. When this
-     * preview is dragged onto a parent document, a copy of that document is added as an annotation.
-     */
-    public static addToPalette = async (doc: Doc) => {
-        if (!doc.savedAsAnno) {
-            const docView = DocumentView.getDocumentView(doc);
-            await docView?.ComponentView?.updateIcon?.(true);
-            const { clone } = await Doc.MakeClone(doc);
-            clone.title = doc.title;
-            const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href;
-            Doc.AddDocToList(Doc.MyAnnos, 'data', makeUserTemplateButtonOrImage(clone, image));
-            doc.savedAsAnno = true;
-        }
-    };
-
-    public static getIcon(group: Doc) {
-        const docView = DocumentView.getDocumentView(group);
-        if (docView) {
-            docView.ComponentView?.updateIcon?.(true);
-            return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 1000));
-        }
-        return undefined;
-    }
-
-    /**
-     * Calls the draw with GPT functions in SmartDrawHandler to allow users to generate drawings straight from
-     * the annotation palette.
-     */
-    @undoBatch
-    generateDrawings = action(async () => {
-        this._isLoading = true;
-        this._props.Document[DocData].data = undefined;
-        for (let i = 0; i < 3; i++) {
-            try {
-                SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
-                this._canInteract = false;
-                if (this._showRegenerate) {
-                    await SmartDrawHandler.Instance.regenerate(this._opts, this._gptRes[i], this._userInput);
-                } else {
-                    await SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity, this._opts.size, this._opts.autoColor);
-                }
-            } catch (e) {
-                console.log('Error generating drawing', e);
-            }
-        }
-        this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput);
-        this._userInput = '';
-        this._isLoading = false;
-        this._showRegenerate = true;
-    });
-
-    @action
-    addDrawing = (drawing: Doc, opts: DrawingOptions, gptRes: string) => {
-        this._gptRes.push(gptRes);
-        drawing[DocData].freeform_fitContentsToBox = true;
-        Doc.AddDocToList(this._props.Document, 'data', drawing);
-    };
-
-    /**
-     * Saves the currently showing, newly generated drawing to the annotation palette and sets the metadata.
-     * AddToPalette() is generically used to add any document to the palette, while this defines the behavior for when a user
-     * presses the "save drawing" button.
-     */
-    saveDrawing = async () => {
-        const cIndex = NumCast(this._props.Document.carousel_index);
-        const focusedDrawing = DocListCast(this._props.Document.data)[cIndex];
-        const docData = focusedDrawing[DocData];
-        docData.title = this._opts.text.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
-        docData.drawingInput = this._opts.text;
-        docData.drawingComplexity = this._opts.complexity;
-        docData.drawingColored = this._opts.autoColor;
-        docData.drawingSize = this._opts.size;
-        docData.drawingData = this._gptRes[cIndex];
-        focusedDrawing.width = this._opts.size;
-        docData.x = this._opts.x;
-        docData.y = this._opts.y;
-        await AnnotationPalette.addToPalette(focusedDrawing);
-        this.resetPalette(true);
-    };
-
-    render() {
-        return (
-            <div className="annotation-palette" style={{ zIndex: 1000 }} onClick={e => e.stopPropagation()}>
-                {this._paletteMode === 'view' && (
-                    <>
-                        <DocumentView
-                            ref={r => (this._docView = r)}
-                            Document={Doc.MyAnnos}
-                            addDocument={undefined}
-                            addDocTab={DocumentViewInternal.addDocTabFunc}
-                            pinToPres={DocumentView.PinDoc}
-                            containerViewPath={returnEmptyDocViewList}
-                            styleProvider={DefaultStyleProvider}
-                            removeDocument={returnFalse}
-                            ScreenToLocalTransform={Transform.Identity}
-                            PanelWidth={this.return170}
-                            PanelHeight={this.return170}
-                            renderDepth={0}
-                            isContentActive={returnTrue}
-                            focus={emptyFunction}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            childFilters={returnEmptyFilter}
-                            childFiltersByRanges={returnEmptyFilter}
-                            searchFilterDocs={returnEmptyDoclist}
-                        />
-                        <Button text="Add" icon={<FontAwesomeIcon icon="square-plus" />} color={SettingsManager.userColor} onClick={() => this.setPaletteMode('create')} />
-                    </>
-                )}
-                {this._paletteMode === 'create' && (
-                    <>
-                        <div className="palette-create">
-                            <input
-                                className="palette-create-input"
-                                aria-label="label-input"
-                                id="new-label"
-                                type="text"
-                                value={this._userInput}
-                                onChange={e => {
-                                    this.setUserInput(e.target.value);
-                                }}
-                                placeholder={this._showRegenerate ? '(Optional) Enter edits' : 'Enter item to draw'}
-                                onKeyDown={this.handleKeyPress}
-                            />
-                            <Button
-                                style={{ alignSelf: 'flex-end' }}
-                                tooltip={this._showRegenerate ? 'Regenerate' : 'Send'}
-                                icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : this._showRegenerate ? <FontAwesomeIcon icon={'rotate'} /> : <AiOutlineSend />}
-                                iconPlacement="right"
-                                color={SettingsManager.userColor}
-                                onClick={this.generateDrawings}
-                            />
-                        </div>
-                        <div className="palette-create-options">
-                            <div className="palette-color">
-                                Color
-                                <Switch
-                                    sx={{
-                                        '& .MuiSwitch-switchBase.Mui-checked': {
-                                            color: SettingsManager.userColor,
-                                        },
-                                        '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
-                                            backgroundColor: SettingsManager.userVariantColor,
-                                        },
-                                    }}
-                                    defaultChecked={true}
-                                    value={this._opts.autoColor}
-                                    size="small"
-                                    onChange={() => this.setColor(!this._opts.autoColor)}
-                                />
-                            </div>
-                            <div className="palette-detail">
-                                Detail
-                                <Slider
-                                    sx={{
-                                        '& .MuiSlider-thumb': {
-                                            color: SettingsManager.userColor,
-                                            '&.Mui-focusVisible, &:hover, &.Mui-active': {
-                                                boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10`,
-                                            },
-                                        },
-                                        '& .MuiSlider-track': {
-                                            color: SettingsManager.userVariantColor,
-                                        },
-                                        '& .MuiSlider-rail': {
-                                            color: SettingsManager.userColor,
-                                        },
-                                    }}
-                                    style={{ width: '80%' }}
-                                    min={1}
-                                    max={10}
-                                    step={1}
-                                    size="small"
-                                    value={this._opts.complexity}
-                                    onChange={(e, val) => {
-                                        this.setDetail(val as number);
-                                    }}
-                                    valueLabelDisplay="auto"
-                                />
-                            </div>
-                            <div className="palette-size">
-                                Size
-                                <Slider
-                                    sx={{
-                                        '& .MuiSlider-thumb': {
-                                            color: SettingsManager.userColor,
-                                            '&.Mui-focusVisible, &:hover, &.Mui-active': {
-                                                boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20`,
-                                            },
-                                        },
-                                        '& .MuiSlider-track': {
-                                            color: SettingsManager.userVariantColor,
-                                        },
-                                        '& .MuiSlider-rail': {
-                                            color: SettingsManager.userColor,
-                                        },
-                                    }}
-                                    style={{ width: '80%' }}
-                                    min={50}
-                                    max={500}
-                                    step={10}
-                                    size="small"
-                                    value={this._opts.size}
-                                    onChange={(e, val) => {
-                                        this.setSize(val as number);
-                                    }}
-                                    valueLabelDisplay="auto"
-                                />
-                            </div>
-                        </div>
-                        <DocumentView
-                            ref={r => (this._docCarouselView = r)}
-                            Document={this._props.Document}
-                            addDocument={undefined}
-                            addDocTab={DocumentViewInternal.addDocTabFunc}
-                            pinToPres={DocumentView.PinDoc}
-                            containerViewPath={returnEmptyDocViewList}
-                            styleProvider={DefaultStyleProvider}
-                            removeDocument={returnFalse}
-                            ScreenToLocalTransform={Transform.Identity}
-                            PanelWidth={this.return170}
-                            PanelHeight={this.return170}
-                            renderDepth={1}
-                            isContentActive={returnTrue}
-                            focus={emptyFunction}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            childFilters={returnEmptyFilter}
-                            childFiltersByRanges={returnEmptyFilter}
-                            searchFilterDocs={returnEmptyDoclist}
-                        />
-                        <div className="palette-buttons">
-                            <Button text="Back" tooltip="Back to All Annotations" icon={<FontAwesomeIcon icon="reply" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(true)} />
-                            <div className="palette-save-reset">
-                                <Button tooltip="Save" icon={<FontAwesomeIcon icon="file-arrow-down" />} color={SettingsManager.userColor} onClick={this.saveDrawing} />
-                                <Button tooltip="Reset" icon={<FontAwesomeIcon icon="rotate-left" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(false)} />
-                            </div>
-                        </div>
-                    </>
-                )}
-            </div>
-        );
-    }
-}
-
-Docs.Prototypes.TemplateMap.set(DocumentType.ANNOPALETTE, {
-    layout: { view: AnnotationPalette, dataField: 'data' },
-    options: { acl: '' },
-});
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index b4635673c..0078dfc76 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -83,7 +83,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
     /**
      *  AddDrawing and RemoveDrawing are defined by the other classes that call the smart draw functions (i.e. 
         CollectionFreeForm, FormattedTextBox, AnnotationPalette) to define how a drawing document should be added
-        or removed in their respective locations (to the freeform canvs, to the annotation palette's preview, etc.)
+        or removed in their respective locations (to the freeform canvas, to the sticker palette's preview, etc.)
     */
     public AddDrawing: (doc: Doc, opts: DrawingOptions, gptRes: string) => void = unimplementedFunction;
     public RemoveDrawing: (useLastContainer: boolean, doc?: Doc) => void = unimplementedFunction;
diff --git a/src/client/views/smartdraw/StickerPalette.scss b/src/client/views/smartdraw/StickerPalette.scss
new file mode 100644
index 000000000..ca99410cf
--- /dev/null
+++ b/src/client/views/smartdraw/StickerPalette.scss
@@ -0,0 +1,56 @@
+.sticker-palette {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    position: absolute;
+    right: 14px;
+    top: 50px;
+    border-radius: 5px;
+    margin: auto;
+}
+
+.palette-create {
+    display: flex;
+    flex-direction: row;
+    width: 170px;
+
+    .palette-create-input {
+        color: black;
+        width: 170px;
+    }
+}
+
+.palette-create-options {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    width: 170px;
+    margin-top: 5px;
+
+    .palette-color {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        width: 40px;
+    }
+
+    .palette-detail,
+    .palette-size {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        width: 60px;
+    }
+}
+
+.palette-buttons {
+    width: 100%;
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+}
+
+.palette-save-reset {
+    display: flex;
+    flex-direction: row;
+}
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
new file mode 100644
index 000000000..468d0fd13
--- /dev/null
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -0,0 +1,362 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Slider, Switch } from '@mui/material';
+import { Button } from 'browndash-components';
+import { action, makeObservable, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { AiOutlineSend } from 'react-icons/ai';
+import ReactLoading from 'react-loading';
+import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
+import { emptyFunction } from '../../../Utils';
+import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
+import { ImageCast, NumCast } from '../../../fields/Types';
+import { ImageField } from '../../../fields/URLField';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { Docs } from '../../documents/Documents';
+import { makeUserTemplateButtonOrImage } from '../../util/DropConverter';
+import { SettingsManager } from '../../util/SettingsManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from '../../util/UndoManager';
+import { ObservableReactComponent } from '../ObservableReactComponent';
+import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider';
+import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+import { FieldView } from '../nodes/FieldView';
+import './StickerPalette.scss';
+import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler';
+
+interface StickerPaletteProps {
+    Document: Doc;
+}
+
+/**
+ * The StickerPalette can be toggled in the lightbox view of a document. The goal of the palette
+ * is to offer an easy way for users to create stickers and  drag and drop them onto a document.
+ * These stickers can technically be of any document type and operate similarly to user templates.
+ * However, the palette is designed to be geared toward ink stickers and image stickers.
+ *
+ * On the "add" side of the palette, there is a way to create a drawing sticker with GPT. Users can
+ * enter the item to draw, toggle different settings, then GPT will generate three versions of the drawing
+ * to choose from. These drawings can then be saved to the palette as stickers.
+ */
+@observer
+export class StickerPalette extends ObservableReactComponent<StickerPaletteProps> {
+    @observable private _paletteMode: 'create' | 'view' = 'view';
+    @observable private _userInput: string = '';
+    @observable private _isLoading: boolean = false;
+    @observable private _canInteract: boolean = true;
+    @observable private _showRegenerate: boolean = false;
+    @observable private _docView: DocumentView | null = null;
+    @observable private _docCarouselView: DocumentView | null = null;
+    @observable private _opts: DrawingOptions = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
+    private _gptRes: string[] = [];
+
+    constructor(props: StickerPaletteProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    public static LayoutString(fieldKey: string) {
+        return FieldView.LayoutString(StickerPalette, fieldKey);
+    }
+
+    Contains = (view: DocumentView) => {
+        return (this._docView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docView)) || (this._docCarouselView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docCarouselView));
+    };
+
+    return170 = () => 170;
+
+    @action
+    handleKeyPress = async (event: React.KeyboardEvent) => {
+        if (event.key === 'Enter') {
+            await this.generateDrawings();
+        }
+    };
+
+    @action
+    setPaletteMode = (mode: 'create' | 'view') => {
+        this._paletteMode = mode;
+    };
+
+    @action
+    setUserInput = (input: string) => {
+        if (!this._isLoading) this._userInput = input;
+    };
+
+    @action
+    setDetail = (detail: number) => {
+        if (this._canInteract) this._opts.complexity = detail;
+    };
+
+    @action
+    setColor = (autoColor: boolean) => {
+        if (this._canInteract) this._opts.autoColor = autoColor;
+    };
+
+    @action
+    setSize = (size: number) => {
+        if (this._canInteract) this._opts.size = size;
+    };
+
+    @action
+    resetPalette = (changePaletteMode: boolean) => {
+        if (changePaletteMode) this.setPaletteMode('view');
+        this.setUserInput('');
+        this.setDetail(5);
+        this.setColor(true);
+        this.setSize(200);
+        this._showRegenerate = false;
+        this._canInteract = true;
+        this._opts = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
+        this._gptRes = [];
+        this._props.Document[DocData].data = undefined;
+    };
+
+    /**
+     * Adds a doc to the sticker palette. Gets a snapshot of the document to use as a preview in the palette. When this
+     * preview is dragged onto a parent document, a copy of that document is added as a sticker.
+     */
+    public static addToPalette = async (doc: Doc) => {
+        if (!doc.savedAsSticker) {
+            const docView = DocumentView.getDocumentView(doc);
+            await docView?.ComponentView?.updateIcon?.(true);
+            const { clone } = await Doc.MakeClone(doc);
+            clone.title = doc.title;
+            const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutFieldKey(clone)]))?.url?.href;
+            Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image));
+            doc.savedAsSticker = true;
+        }
+    };
+
+    public static getIcon(group: Doc) {
+        const docView = DocumentView.getDocumentView(group);
+        if (docView) {
+            docView.ComponentView?.updateIcon?.(true);
+            return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 1000));
+        }
+        return undefined;
+    }
+
+    /**
+     * Calls the draw with GPT functions in SmartDrawHandler to allow users to generate drawings straight from
+     * the sticker palette.
+     */
+    @undoBatch
+    generateDrawings = action(async () => {
+        this._isLoading = true;
+        this._props.Document[DocData].data = undefined;
+        for (let i = 0; i < 3; i++) {
+            try {
+                SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
+                this._canInteract = false;
+                if (this._showRegenerate) {
+                    await SmartDrawHandler.Instance.regenerate(this._opts, this._gptRes[i], this._userInput);
+                } else {
+                    await SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity, this._opts.size, this._opts.autoColor);
+                }
+            } catch (e) {
+                console.log('Error generating drawing', e);
+            }
+        }
+        this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput);
+        this._userInput = '';
+        this._isLoading = false;
+        this._showRegenerate = true;
+    });
+
+    @action
+    addDrawing = (drawing: Doc, opts: DrawingOptions, gptRes: string) => {
+        this._gptRes.push(gptRes);
+        drawing[DocData].freeform_fitContentsToBox = true;
+        Doc.AddDocToList(this._props.Document, 'data', drawing);
+    };
+
+    /**
+     * Saves the currently showing, newly generated drawing to the sticker palette and sets the metadata.
+     * AddToPalette() is generically used to add any document to the palette, while this defines the behavior for when a user
+     * presses the "save drawing" button.
+     */
+    saveDrawing = async () => {
+        const cIndex = NumCast(this._props.Document.carousel_index);
+        const focusedDrawing = DocListCast(this._props.Document.data)[cIndex];
+        const docData = focusedDrawing[DocData];
+        docData.title = this._opts.text.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
+        docData.drawingInput = this._opts.text;
+        docData.drawingComplexity = this._opts.complexity;
+        docData.drawingColored = this._opts.autoColor;
+        docData.drawingSize = this._opts.size;
+        docData.drawingData = this._gptRes[cIndex];
+        focusedDrawing.width = this._opts.size;
+        docData.x = this._opts.x;
+        docData.y = this._opts.y;
+        await StickerPalette.addToPalette(focusedDrawing);
+        this.resetPalette(true);
+    };
+
+    render() {
+        return (
+            <div className="sticker-palette" style={{ zIndex: 1000 }} onClick={e => e.stopPropagation()}>
+                {this._paletteMode === 'view' && (
+                    <>
+                        <DocumentView
+                            ref={r => (this._docView = r)}
+                            Document={Doc.MyStickers}
+                            addDocument={undefined}
+                            addDocTab={DocumentViewInternal.addDocTabFunc}
+                            pinToPres={DocumentView.PinDoc}
+                            containerViewPath={returnEmptyDocViewList}
+                            styleProvider={DefaultStyleProvider}
+                            removeDocument={returnFalse}
+                            ScreenToLocalTransform={Transform.Identity}
+                            PanelWidth={this.return170}
+                            PanelHeight={this.return170}
+                            renderDepth={0}
+                            isContentActive={returnTrue}
+                            focus={emptyFunction}
+                            whenChildContentsActiveChanged={emptyFunction}
+                            childFilters={returnEmptyFilter}
+                            childFiltersByRanges={returnEmptyFilter}
+                            searchFilterDocs={returnEmptyDoclist}
+                        />
+                        <Button text="Add" icon={<FontAwesomeIcon icon="square-plus" />} color={SettingsManager.userColor} onClick={() => this.setPaletteMode('create')} />
+                    </>
+                )}
+                {this._paletteMode === 'create' && (
+                    <>
+                        <div className="palette-create">
+                            <input
+                                className="palette-create-input"
+                                aria-label="label-input"
+                                id="new-label"
+                                type="text"
+                                value={this._userInput}
+                                onChange={e => {
+                                    this.setUserInput(e.target.value);
+                                }}
+                                placeholder={this._showRegenerate ? '(Optional) Enter edits' : 'Enter item to draw'}
+                                onKeyDown={this.handleKeyPress}
+                            />
+                            <Button
+                                style={{ alignSelf: 'flex-end' }}
+                                tooltip={this._showRegenerate ? 'Regenerate' : 'Send'}
+                                icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : this._showRegenerate ? <FontAwesomeIcon icon={'rotate'} /> : <AiOutlineSend />}
+                                iconPlacement="right"
+                                color={SettingsManager.userColor}
+                                onClick={this.generateDrawings}
+                            />
+                        </div>
+                        <div className="palette-create-options">
+                            <div className="palette-color">
+                                Color
+                                <Switch
+                                    sx={{
+                                        '& .MuiSwitch-switchBase.Mui-checked': {
+                                            color: SettingsManager.userColor,
+                                        },
+                                        '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
+                                            backgroundColor: SettingsManager.userVariantColor,
+                                        },
+                                    }}
+                                    defaultChecked={true}
+                                    value={this._opts.autoColor}
+                                    size="small"
+                                    onChange={() => this.setColor(!this._opts.autoColor)}
+                                />
+                            </div>
+                            <div className="palette-detail">
+                                Detail
+                                <Slider
+                                    sx={{
+                                        '& .MuiSlider-thumb': {
+                                            color: SettingsManager.userColor,
+                                            '&.Mui-focusVisible, &:hover, &.Mui-active': {
+                                                boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10`,
+                                            },
+                                        },
+                                        '& .MuiSlider-track': {
+                                            color: SettingsManager.userVariantColor,
+                                        },
+                                        '& .MuiSlider-rail': {
+                                            color: SettingsManager.userColor,
+                                        },
+                                    }}
+                                    style={{ width: '80%' }}
+                                    min={1}
+                                    max={10}
+                                    step={1}
+                                    size="small"
+                                    value={this._opts.complexity}
+                                    onChange={(e, val) => {
+                                        this.setDetail(val as number);
+                                    }}
+                                    valueLabelDisplay="auto"
+                                />
+                            </div>
+                            <div className="palette-size">
+                                Size
+                                <Slider
+                                    sx={{
+                                        '& .MuiSlider-thumb': {
+                                            color: SettingsManager.userColor,
+                                            '&.Mui-focusVisible, &:hover, &.Mui-active': {
+                                                boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20`,
+                                            },
+                                        },
+                                        '& .MuiSlider-track': {
+                                            color: SettingsManager.userVariantColor,
+                                        },
+                                        '& .MuiSlider-rail': {
+                                            color: SettingsManager.userColor,
+                                        },
+                                    }}
+                                    style={{ width: '80%' }}
+                                    min={50}
+                                    max={500}
+                                    step={10}
+                                    size="small"
+                                    value={this._opts.size}
+                                    onChange={(e, val) => {
+                                        this.setSize(val as number);
+                                    }}
+                                    valueLabelDisplay="auto"
+                                />
+                            </div>
+                        </div>
+                        <DocumentView
+                            ref={r => (this._docCarouselView = r)}
+                            Document={this._props.Document}
+                            addDocument={undefined}
+                            addDocTab={DocumentViewInternal.addDocTabFunc}
+                            pinToPres={DocumentView.PinDoc}
+                            containerViewPath={returnEmptyDocViewList}
+                            styleProvider={DefaultStyleProvider}
+                            removeDocument={returnFalse}
+                            ScreenToLocalTransform={Transform.Identity}
+                            PanelWidth={this.return170}
+                            PanelHeight={this.return170}
+                            renderDepth={1}
+                            isContentActive={returnTrue}
+                            focus={emptyFunction}
+                            whenChildContentsActiveChanged={emptyFunction}
+                            childFilters={returnEmptyFilter}
+                            childFiltersByRanges={returnEmptyFilter}
+                            searchFilterDocs={returnEmptyDoclist}
+                        />
+                        <div className="palette-buttons">
+                            <Button text="Back" tooltip="Back to All Stickers" icon={<FontAwesomeIcon icon="reply" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(true)} />
+                            <div className="palette-save-reset">
+                                <Button tooltip="Save" icon={<FontAwesomeIcon icon="file-arrow-down" />} color={SettingsManager.userColor} onClick={this.saveDrawing} />
+                                <Button tooltip="Reset" icon={<FontAwesomeIcon icon="rotate-left" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(false)} />
+                            </div>
+                        </div>
+                    </>
+                )}
+            </div>
+        );
+    }
+}
+
+Docs.Prototypes.TemplateMap.set(DocumentType.ANNOPALETTE, {
+    layout: { view: StickerPalette, dataField: 'data' },
+    options: { acl: '' },
+});
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 81241f9fe..c77c2a77d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -236,7 +236,7 @@ export class Doc extends RefField {
     public static get MyPublishedDocs()       { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs).concat(DocListCast(DocCast(Doc.UserDoc().myPublishedDocs)?.data)); } // prettier-ignore
     public static get MyDashboards()          { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore
     public static get MyTemplates()           { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore
-    public static get MyAnnos()               { return DocCast(Doc.UserDoc().myAnnos); } // prettier-ignore
+    public static get MyStickers()               { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore
     public static get MyLightboxDrawings()    { return DocCast(Doc.UserDoc().myLightboxDrawings); } // prettier-ignore
     public static get MyImports()             { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore
     public static get MyFilesystem()          { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore
-- 
cgit v1.2.3-70-g09d2


From f7bc3b892bbb3f559fa8bac1ebe9d2d747a5728c Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Wed, 30 Oct 2024 20:14:53 -0400
Subject: blending changes

---
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 9642dcc31..8f6a7e023 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -435,7 +435,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             const newImgDoc = await createNewImgDoc(finalImg, firstDoc);
             if (newImgDoc) {
                 const docData = newImgDoc[DocData];
-                // docData.backgroundColor = 'transparent';
+                docData.backgroundColor = 'transparent';
+                docData.disableMixBlend = true;
                 if (firstDoc) setIsFirstDoc(false);
                 setEdits([...prevEdits, { url: finalImgURL, saveRes: undefined }]);
             }
-- 
cgit v1.2.3-70-g09d2


From 8847d6cf5a0eaeeddaefc71d0202247b4d2d096b Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Sun, 3 Nov 2024 16:32:28 -0500
Subject: working on smart draw width bugs

---
 src/client/util/CurrentUserUtils.ts                                    | 2 +-
 src/client/views/LightboxView.tsx                                      | 3 +--
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx    | 2 +-
 src/client/views/nodes/imageEditor/ImageEditor.tsx                     | 2 +-
 src/client/views/smartdraw/SmartDrawHandler.tsx                        | 2 +-
 src/fields/InkField.ts                                                 | 2 +-
 6 files changed, 6 insertions(+), 7 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 009cd7de7..99faafe38 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -779,7 +779,7 @@ pie title Minerals in my tap water
             { title: " Size",      toolTip: "Size of area pencil eraser",   btnType: ButtonType.NumberSliderButton,               toolType: "eraserWidth",          ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"NotRadiusEraser()"}, numBtnMin: 1, linearBtnWidth:40},
             { title: "Mask",       toolTip: "Make Stroke a Stencil Mask",   btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                                 scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
             { title: "Labels",     toolTip: "Show Labels Inside Shapes",    btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels",                                  scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}},
-            { title: "Smart Draw", toolTip: "Draw with GPT",                btnType: ButtonType.ToggleButton, icon: "user-pen",   toolType: "smartdraw",                               scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'},  funcs: {hidden: "IsNoviceMode()"}},
+            { title: "Smart Draw", toolTip: "Draw with GPT",                btnType: ButtonType.ToggleButton, icon: "user-pen",   toolType: InkTool.SmartDraw,                               scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'},  funcs: {hidden: "IsNoviceMode()"}},
         ];
     }
 
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 65de85008..ce6b26323 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -35,7 +35,7 @@ type LightboxSavedState = { [key: string]: FieldResult; }; // prettier-ignore
 @observer
 export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
     /**
-     * Determines whether a DocumentView is descendant of the lightbox view (or any of its pop-ups like the annotationPalette)
+     * Determines whether a DocumentView is descendant of the lightbox view (or any of its pop-ups like the stickerPalette)
      * @param view
      * @returns true if a DocumentView is descendant of the lightbox view
      */
@@ -211,7 +211,6 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> {
     };
     togglePalette = () => {
         this._showPalette = !this._showPalette;
-        // if (this._showPalette === false) AnnotationPalette.Instance.resetPalette(true);
     };
     togglePen = () => {
         Doc.ActiveTool = Doc.ActiveTool === InkTool.Ink ? InkTool.None : InkTool.Ink;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 65b2702c6..4f62b6fbb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1285,7 +1285,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     addDrawing = (doc: Doc, opts: DrawingOptions, gptRes: string) => {
         const docData = doc[DocData];
         docData.title = opts.text.match(/^(.*?)~~~.*$/)?.[1] || opts.text;
-        docData.width = opts.size;
+        docData._width = opts.size;
         docData.drawingInput = opts.text;
         docData.drawingComplexity = opts.complexity;
         docData.drawingColored = opts.autoColor;
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 8f6a7e023..c8fe5adc3 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -426,7 +426,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         image.onload = async () => {
             let finalImg: HTMLImageElement | undefined = image;
             let finalImgURL: string = url;
-            if (currCutType == BrushMode.IN) {
+            if (currCutType == BrushMode.IN || currCutType == BrushMode.LINE_IN) {
                 const croppedData = cropImage(image, minX, maxX, minY, maxY);
                 finalImg = croppedData;
                 finalImgURL = croppedData.src;
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 4c5be047b..83360bb6e 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -82,7 +82,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
 
     /**
      *  AddDrawing and RemoveDrawing are defined by the other classes that call the smart draw functions (i.e. 
-        CollectionFreeForm, FormattedTextBox, AnnotationPalette) to define how a drawing document should be added
+        CollectionFreeForm, FormattedTextBox, StickerPalette) to define how a drawing document should be added
         or removed in their respective locations (to the freeform canvas, to the sticker palette's preview, etc.)
     */
     public AddDrawing: (doc: Doc, opts: DrawingOptions, gptRes: string) => void = unimplementedFunction;
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
index f3ff5f11f..702301b31 100644
--- a/src/fields/InkField.ts
+++ b/src/fields/InkField.ts
@@ -11,7 +11,7 @@ export enum InkTool {
     None = 'None',
     Ink = 'Ink',
     Eraser = 'Eraser', // not a real tool, but a class of tools
-    SmartDraw = 'Smartdraw',
+    SmartDraw = 'smartdraw',
 }
 
 export enum InkInkTool {
-- 
cgit v1.2.3-70-g09d2


From 0fbe7696fa23cd0621d4053b430cec01d72f6801 Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Mon, 25 Nov 2024 22:54:11 -0500
Subject: bug fixes to image cropping

---
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 20 ++++++--------------
 1 file changed, 6 insertions(+), 14 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 5c7e09645..5c4c83eef 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -451,21 +451,13 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         const croppedCanvas = document.createElement('canvas');
         const croppedCtx = croppedCanvas.getContext('2d');
         if (!croppedCtx) return image;
-        croppedCanvas.width = maxX - minX;
-        croppedCanvas.height = maxY - minY;
+        const cropWidth = Math.abs(maxX - minX);
+        const cropHeight = Math.abs(maxY - minY);
+        croppedCanvas.width = cropWidth;
+        croppedCanvas.height = cropHeight;
         croppedCtx.globalCompositeOperation = 'source-over';
-        croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
-        croppedCtx.drawImage(
-            image,
-            minX,
-            minY,
-            maxX - minX,
-            maxY - minY, // Source image crop area
-            0,
-            0,
-            maxX - minX,
-            maxY - minY // Destination area on the canvas
-        );
+        croppedCtx.clearRect(0, 0, cropWidth, cropHeight);
+        croppedCtx.drawImage(image, minX, minY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
         const croppedURL = croppedCanvas.toDataURL();
         const croppedImage = new Image();
         croppedImage.src = croppedURL;
-- 
cgit v1.2.3-70-g09d2


From e6a4b7c05899ee6d6a821666b72023856081d5ca Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Fri, 13 Dec 2024 17:45:34 -0500
Subject: added comments, refactored duplicate code, and resolved lint errors

---
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 137 +++++++++++----------
 .../imageEditorUtils/imageEditorInterfaces.ts      |  18 +--
 2 files changed, 82 insertions(+), 73 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 5c4c83eef..a742673e0 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -22,11 +22,11 @@ import { ImageEditorData } from '../ImageBox';
 import { OpenWhereMod } from '../OpenWhere';
 import './ImageEditor.scss';
 import { ApplyFuncButtons, ImageToolButton } from './ImageEditorButtons';
-import { BrushHandler, BrushType } from './imageEditorUtils/BrushHandler';
+import { BrushHandler } from './imageEditorUtils/BrushHandler';
 import { APISuccess, ImageUtility } from './imageEditorUtils/ImageHandler';
 import { PointerHandler } from './imageEditorUtils/PointerHandler';
-import { activeColor, bgColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants';
-import { BrushMode, CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces';
+import { activeColor, bgColor, brushWidthOffset, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants';
+import { CutMode, CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces';
 import { DocumentView } from '../DocumentView';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ImageField } from '../../../../fields/URLField';
@@ -64,7 +64,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         width: canvasSize,
         height: canvasSize,
     });
-    const [cutType, setCutType] = useState<BrushMode>(BrushMode.IN);
+    const [cutType, setCutType] = useState<CutMode>(CutMode.IN);
     // whether to create a new collection or not
     const [isNewCollection, setIsNewCollection] = useState(true);
     // the current image in the main canvas
@@ -183,7 +183,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                 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);
+            const pts = BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor);
             cutPts.current.push(...pts);
         };
 
@@ -283,7 +283,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         }));
     };
 
-    // Get AI Edit
+    // Get AI Edit for Generative Fill
     const getEdit = async () => {
         const img = currImg.current;
         if (!img) return;
@@ -304,32 +304,14 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
 
             // 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);
-                }
+                createNewCollection();
             } else {
                 childrenDocs.current = [];
             }
-
+            if (!(originalImg.current && imageRootDoc)) return;
+            // add the doc to the main freeform
+            // eslint-disable-next-line no-use-before-define
+            await createNewImgDoc(originalImg.current, true);
             originalImg.current = currImg.current;
             originalDoc.current = parentDoc.current;
             const { urls } = res as APISuccess;
@@ -356,23 +338,32 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         setLoading(false);
     };
 
-    const cutImage = async (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], firstDoc: boolean) => {
+    /**
+     * This function performs image cutting based on the inputted BrushMode. There are currently four ways to cut images:
+     *      1. By outlining the area that should be kept (BrushMode.IN)
+     *      2. By outlining the area that should be removed (BrushMode.OUT)
+     *      3. By drawing in the area that should be kept (where the image is brushed, the image will remain and everything else will be removed) (BrushMode.DRAW_IN)
+     *      4. By drawing the area that she be removed, so this operates as an eraser (BrushMode.ERASE)
+     * @param currCutType BrushMode enum that determines what kind of cutting operation to perform
+     * @param firstDoc boolean for whether it's the first edited image. This is for positioning of the edited images when they render on the canvas.
+     */
+    const cutImage = async (currCutType: CutMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], firstDoc: boolean) => {
         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!
+        setLoading(true);
         const currPts = [...cutPts.current];
-        if (currCutType !== BrushMode.LINE_OUT) handleReset(); // gets rid of the visible brush strokes (mostly needed for line_in) unless it's erasing (which depends on the brush strokes)
+        if (currCutType !== CutMode.ERASE) handleReset(); // gets rid of the visible brush strokes (mostly needed for line_in) unless it's erasing (which depends on the brush strokes)
         let minX = img.width;
         let maxX = 0;
         let minY = img.height;
         let maxY = 0;
+        // currPts is populated by the brush strokes' points, so this code is drawing a path along the points
         if (currPts.length) {
             ctx.beginPath();
             ctx.moveTo(currPts[0].x, currPts[0].y);
@@ -383,44 +374,30 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                 maxX = Math.max(currPts[i].x, maxX);
                 maxY = Math.max(currPts[i].y, maxY);
             }
-            switch (currCutType) {
-                case BrushMode.IN:
+            switch (
+                currCutType // use different canvas operations depending on the type of cutting we're applying
+            ) {
+                case CutMode.IN:
                     ctx.closePath();
                     ctx.globalCompositeOperation = 'destination-in';
                     ctx.fill();
                     break;
-                case BrushMode.OUT:
+                case CutMode.OUT:
                     ctx.closePath();
                     ctx.globalCompositeOperation = 'destination-out';
                     ctx.fill();
                     break;
-                case BrushMode.LINE_IN:
+                case CutMode.DRAW_IN:
                     ctx.globalCompositeOperation = 'destination-in';
-                    ctx.lineWidth = brushWidth + 10; // added offset because width gets cut off a little bit
+                    ctx.lineWidth = brushWidth + brushWidthOffset; // added offset because width gets cut off a little bit
                     ctx.stroke();
                     break;
             }
         }
 
-        const url = canvas.toDataURL(); // this does the same thing as convert img to canvasurl
+        const url = canvas.toDataURL();
         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);
-            }
+            createNewCollection();
         }
 
         const image = new Image();
@@ -428,7 +405,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         image.onload = async () => {
             let finalImg: HTMLImageElement | undefined = image;
             let finalImgURL: string = url;
-            if (currCutType == BrushMode.IN || currCutType == BrushMode.LINE_IN) {
+            // crop the image for these brush modes to remove excess blank space around the image contents
+            if (currCutType == CutMode.IN || currCutType == CutMode.DRAW_IN) {
                 const croppedData = cropImage(image, minX, maxX, minY, maxY);
                 finalImg = croppedData;
                 finalImgURL = croppedData.src;
@@ -436,6 +414,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             currImg.current = finalImg;
             const newImgDoc = await createNewImgDoc(finalImg, firstDoc);
             if (newImgDoc) {
+                // set the image to transparent to remove the background / brushstrokes
                 const docData = newImgDoc[DocData];
                 docData.backgroundColor = 'transparent';
                 docData.disableMixBlend = true;
@@ -447,6 +426,34 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         };
     };
 
+    /**
+     * Creates a new collection to put the image edits on. Adds to a new tab on the right if "Create New Collection" is checked.
+     * @returns
+     */
+    const createNewCollection = () => {
+        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);
+        }
+    };
+
+    /**
+     * This function crops an image based on the inputted dimensions. This is used to automatically adjust the images that are
+     * edited to be smaller than the original (i.e. for cutting into a small part of the image)
+     */
     const cropImage = (image: HTMLImageElement, minX: number, maxX: number, minY: number, maxY: number) => {
         const croppedCanvas = document.createElement('canvas');
         const croppedCtx = croppedCanvas.getContext('2d');
@@ -617,36 +624,36 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                                 style={{ width: '100%' }}
                                 text="Keep in"
                                 type={Type.TERT}
-                                color={cutType == BrushMode.IN ? SettingsManager.userColor : bgColor}
+                                color={cutType == CutMode.IN ? SettingsManager.userColor : bgColor}
                                 onClick={() => {
-                                    setCutType(BrushMode.IN);
+                                    setCutType(CutMode.IN);
                                 }}
                             />
                             <Button
                                 style={{ width: '100%' }}
                                 text="Keep out"
                                 type={Type.TERT}
-                                color={cutType == BrushMode.OUT ? SettingsManager.userColor : bgColor}
+                                color={cutType == CutMode.OUT ? SettingsManager.userColor : bgColor}
                                 onClick={() => {
-                                    setCutType(BrushMode.OUT);
+                                    setCutType(CutMode.OUT);
                                 }}
                             />
                             <Button
                                 style={{ width: '100%' }}
                                 text="Draw in"
                                 type={Type.TERT}
-                                color={cutType == BrushMode.LINE_IN ? SettingsManager.userColor : bgColor}
+                                color={cutType == CutMode.DRAW_IN ? SettingsManager.userColor : bgColor}
                                 onClick={() => {
-                                    setCutType(BrushMode.LINE_IN);
+                                    setCutType(CutMode.DRAW_IN);
                                 }}
                             />
                             <Button
                                 style={{ width: '100%' }}
                                 text="Erase"
                                 type={Type.TERT}
-                                color={cutType == BrushMode.LINE_OUT ? SettingsManager.userColor : bgColor}
+                                color={cutType == CutMode.ERASE ? SettingsManager.userColor : bgColor}
                                 onClick={() => {
-                                    setCutType(BrushMode.LINE_OUT);
+                                    setCutType(CutMode.ERASE);
                                 }}
                             />
                         </div>
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
index 75659fc53..a14b55439 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
@@ -17,24 +17,26 @@ export enum ImageToolType {
     Cut = 'cut',
 }
 
+export enum CutMode {
+    IN,
+    OUT,
+    DRAW_IN,
+    ERASE,
+}
+
 export interface ImageEditTool {
     type: ImageToolType;
     name: string;
     btnText: string;
     icon: IconProp;
-    applyFunc: (currCutType: BrushMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], isFirstDoc: boolean) => Promise<void>;
+    // this is the function that the image tool applies, so it can be defined depending on the tool
+    applyFunc: (currCutType: CutMode, brushWidth: number, prevEdits: { url: string; saveRes: Doc | undefined }[], isFirstDoc: boolean) => Promise<void>;
+    // these optional parameters are here because different tools require different brush sizes and defaults
     sliderMin?: number;
     sliderMax?: number;
     sliderDefault?: number;
 }
 
-export enum BrushMode {
-    IN,
-    OUT,
-    LINE_IN,
-    LINE_OUT,
-}
-
 export interface ImageDimensions {
     width: number;
     height: number;
-- 
cgit v1.2.3-70-g09d2


From e7162baa77a851d73037c63b75fac6a5c07f9b61 Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Sat, 14 Dec 2024 14:13:59 -0500
Subject: replaced await in loop, added a componentWillUnmount to reset sticker
 palette

---
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 10 ++-
 .../imageEditor/imageEditorUtils/BrushHandler.ts   |  8 +-
 .../imageEditorUtils/imageEditorConstants.ts       |  1 +
 src/client/views/smartdraw/DrawingFillHandler.tsx  | 96 +++++++++++-----------
 src/client/views/smartdraw/StickerPalette.tsx      | 26 +++---
 5 files changed, 70 insertions(+), 71 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index a742673e0..a39878924 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -28,9 +28,6 @@ import { PointerHandler } from './imageEditorUtils/PointerHandler';
 import { activeColor, bgColor, brushWidthOffset, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants';
 import { CutMode, 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';
 import { SettingsManager } from '../../../util/SettingsManager';
 
@@ -43,6 +40,13 @@ interface GenerativeFillProps {
 
 // Added field on image doc: gen_fill_children: List of children Docs
 
+/**
+ * The image editor interface can be accessed by opening a document's context menu, then going to Options --> Open Image Editor.
+ * The image editor supports various operations on images. Currently, there is a Generative Fill feature that allows users to erase
+ * part of an image, add an optional prompt, and send this to GPT. GPT then returns a newly generated image that replaces the erased
+ * portion based on the optional prompt. There is also an image cutting tool that allows users to cut images in different ways to
+ * reshape the images, take out portions of images, and overall use them more creatively (see the header comment for cutImage() for more information).
+ */
 const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => {
     const canvasRef = useRef<HTMLCanvasElement>(null);
     const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts
index a9fe02d4f..ed39375e0 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts
@@ -1,12 +1,6 @@
 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 */) => {
@@ -20,7 +14,7 @@ export class BrushHandler {
         ctx.closePath();
     };
 
-    static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, brushType: BrushType) => {
+    static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string) => {
         const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
         const pts: Point[] = [];
         for (let i = 0; i < dist; i += 5) {
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts
index 4772304bc..594d6d9fc 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts
@@ -3,6 +3,7 @@ export const freeformRenderSize = 300;
 export const offsetDistanceY = freeformRenderSize + 400;
 export const offsetX = 200;
 export const newCollectionSize = 500;
+export const brushWidthOffset = 10;
 
 export const activeColor = '#1976d2';
 export const eraserColor = '#e1e9ec';
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index c3c762181..8f71b150d 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -1,50 +1,50 @@
-import { action, makeObservable } from 'mobx';
-import { observer } from 'mobx-react';
-import React from 'react';
-import { Doc } from '../../../fields/Doc';
-import { ImageCast } from '../../../fields/Types';
-import { ImageField } from '../../../fields/URLField';
-import { Docs } from '../../documents/Documents';
-import { Networking } from '../../Network';
-import { makeUserTemplateButtonOrImage } from '../../util/DropConverter';
-import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
-import { ImageUtility } from '../nodes/imageEditor/imageEditorUtils/ImageHandler';
-import { OpenWhere } from '../nodes/OpenWhere';
-import { ObservableReactComponent } from '../ObservableReactComponent';
+// import { action, makeObservable } from 'mobx';
+// import { observer } from 'mobx-react';
+// import React from 'react';
+// import { Doc } from '../../../fields/Doc';
+// import { ImageCast } from '../../../fields/Types';
+// import { ImageField } from '../../../fields/URLField';
+// import { Docs } from '../../documents/Documents';
+// import { Networking } from '../../Network';
+// import { makeUserTemplateButtonOrImage } from '../../util/DropConverter';
+// import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
+// import { ImageUtility } from '../nodes/imageEditor/imageEditorUtils/ImageHandler';
+// import { OpenWhere } from '../nodes/OpenWhere';
+// import { ObservableReactComponent } from '../ObservableReactComponent';
 
-export class DrawingFillHandler {
-    static drawingToImage = async (drawing: Doc, prompt: string) => {
-        const imageField = await DocumentView.GetDocImage(drawing);
-        if (!imageField) return;
-        const { href } = ImageCast(imageField).url;
-        const hrefParts = href.split('.');
-        const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
-        try {
-            const response = await fetch(hrefComplete);
-            const blob: Blob = await response.blob();
-            const strength: number = 100;
-            const img = await Networking.PostToServer('/queryFireflyImage', { prompt, blob, strength });
-            DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, {}), OpenWhere.addRight);
-            // Networking.PostToServer('/queryFireflyImage', { prompt, blob, strength }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, {}), OpenWhere.addRight));
-        } catch (error) {
-            console.error('Error fetching image:', error);
-            return;
-        }
+// export class DrawingFillHandler {
+//     static drawingToImage = async (drawing: Doc, prompt: string) => {
+//         const imageField = await DocumentView.GetDocImage(drawing);
+//         if (!imageField) return;
+//         const { href } = ImageCast(imageField).url;
+//         const hrefParts = href.split('.');
+//         const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
+//         try {
+//             const response = await fetch(hrefComplete);
+//             const blob: Blob = await response.blob();
+//             const strength: number = 100;
+//             const img = await Networking.PostToServer('/queryFireflyImage', { prompt, blob, strength });
+//             DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, {}), OpenWhere.addRight);
+//             // Networking.PostToServer('/queryFireflyImage', { prompt, blob, strength }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, {}), OpenWhere.addRight));
+//         } catch (error) {
+//             console.error('Error fetching image:', error);
+//             return;
+//         }
 
-        // const image = new Image();
-        // image.src = imageField.url?.href;
-        // // image.onload = async () => {
-        //     const canvas = document.createElement('canvas');
-        //     canvas.width = image.width;
-        //     canvas.height = image.height;
-        //     const ctx = canvas.getContext('2d');
-        //     if (!ctx) return;
-        //     ctx.globalCompositeOperation = 'source-over';
-        //     ctx.clearRect(0, 0, image.width, image.height);
-        //     ctx.drawImage(image, 0, 0);
-        // const blob: Blob = await ImageUtility.canvasToBlob(canvas);
-        // const strength: number = 100;
-        // Networking.PostToServer('/queryFireflyImage', { prompt, blob, strength }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, {}), OpenWhere.addRight));
-        // };
-    };
-}
+//         // const image = new Image();
+//         // image.src = imageField.url?.href;
+//         // // image.onload = async () => {
+//         //     const canvas = document.createElement('canvas');
+//         //     canvas.width = image.width;
+//         //     canvas.height = image.height;
+//         //     const ctx = canvas.getContext('2d');
+//         //     if (!ctx) return;
+//         //     ctx.globalCompositeOperation = 'source-over';
+//         //     ctx.clearRect(0, 0, image.width, image.height);
+//         //     ctx.drawImage(image, 0, 0);
+//         // const blob: Blob = await ImageUtility.canvasToBlob(canvas);
+//         // const strength: number = 100;
+//         // Networking.PostToServer('/queryFireflyImage', { prompt, blob, strength }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, {}), OpenWhere.addRight));
+//         // };
+//     };
+// }
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
index 468d0fd13..d56878f10 100644
--- a/src/client/views/smartdraw/StickerPalette.tsx
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -56,6 +56,10 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
         makeObservable(this);
     }
 
+    componentWillUnmount() {
+        this.resetPalette(true);
+    }
+
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(StickerPalette, fieldKey);
     }
@@ -145,19 +149,15 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
     generateDrawings = action(async () => {
         this._isLoading = true;
         this._props.Document[DocData].data = undefined;
-        for (let i = 0; i < 3; i++) {
-            try {
-                SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
-                this._canInteract = false;
-                if (this._showRegenerate) {
-                    await SmartDrawHandler.Instance.regenerate(this._opts, this._gptRes[i], this._userInput);
-                } else {
-                    await SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity, this._opts.size, this._opts.autoColor);
-                }
-            } catch (e) {
-                console.log('Error generating drawing', e);
-            }
-        }
+        SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
+        this._canInteract = false;
+        await Promise.all(
+            Array.from({ length: 3 }).map((_, i) => {
+                return this._showRegenerate
+                    ? SmartDrawHandler.Instance.regenerate(this._opts, this._gptRes[i], this._userInput)
+                    : SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity, this._opts.size, this._opts.autoColor);
+            })
+        );
         this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput);
         this._userInput = '';
         this._isLoading = false;
-- 
cgit v1.2.3-70-g09d2


From 0eff48b757ca81860a883d25e147b8a869e5fe00 Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Mon, 30 Dec 2024 23:35:24 -0500
Subject: created image regeneration with dialogue

---
 src/client/apis/gpt/GPT.ts                         |  39 +++-
 src/client/documents/Documents.ts                  |   4 +
 src/client/views/PropertiesView.scss               |   1 +
 src/client/views/PropertiesView.tsx                |  12 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  22 +--
 src/client/views/nodes/ImageBox.tsx                |  10 ++
 src/client/views/nodes/imageEditor/ImageEditor.tsx |   2 +-
 src/client/views/pdf/AnchorMenu.tsx                |   1 +
 src/client/views/smartdraw/DrawingFillHandler.tsx  |  11 +-
 src/client/views/smartdraw/SmartDrawHandler.scss   |   8 +-
 src/client/views/smartdraw/SmartDrawHandler.tsx    | 197 ++++++++++++++-------
 src/client/views/smartdraw/StickerPalette.tsx      |   1 +
 src/server/ApiManagers/FireflyManager.ts           |  20 ++-
 src/server/DashUploadUtils.ts                      |   1 -
 14 files changed, 238 insertions(+), 91 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 03380e4d6..9241eb120 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -249,6 +249,41 @@ const gptHandwriting = async (src: string): Promise<string> => {
     }
 };
 
+const gptDescribeImage = async (image: string): Promise<string> => {
+    try {
+        const response = await openai.chat.completions.create({
+            model: 'gpt-4o',
+            temperature: 0,
+            messages: [
+                {
+                    role: 'user',
+                    content: [
+                        {
+                            type: 'text',
+                            text: `Identify what this drawing is, naming as many elements and their location in the drawing as possible`,
+                        },
+                        {
+                            type: 'image_url',
+                            image_url: {
+                                url: `${image}`,
+                                detail: 'low',
+                            },
+                        },
+                    ],
+                },
+            ],
+        });
+        if (response.choices[0].message.content) {
+            console.log('GPT DESCRIPTION', response.choices[0].message.content);
+            return response.choices[0].message.content;
+        }
+        return 'Unknown drawing';
+    } catch (err) {
+        console.log(err);
+        return 'Error connecting with API';
+    }
+};
+
 const gptDrawingColor = async (image: string, coords: string[]): Promise<string> => {
     try {
         const response = await openai.chat.completions.create({
@@ -276,11 +311,11 @@ const gptDrawingColor = async (image: string, coords: string[]): Promise<string>
         if (response.choices[0].message.content) {
             return response.choices[0].message.content;
         }
-        return 'Missing labels';
+        return 'Unknown drawing';
     } catch (err) {
         console.log(err);
         return 'Error connecting with API';
     }
 };
 
-export { gptAPICall, gptImageCall, GPTCallType, gptImageLabel, gptGetEmbedding, gptHandwriting, gptDrawingColor };
+export { gptAPICall, gptImageCall, GPTCallType, gptImageLabel, gptGetEmbedding, gptHandwriting, gptDescribeImage, gptDrawingColor };
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c51c1645d..785af3409 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -516,6 +516,10 @@ export class DocumentOptions {
 
     card_sort?: STRt = new StrInfo('way cards are sorted in deck view');
     card_sort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending');
+
+    ai_generated?: boolean; // to mark items as ai generated
+    firefly_seed?: number;
+    firefly_prompt?: string;
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 693c75ebf..7866e67e7 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -642,6 +642,7 @@
 
 .smooth,
 .color,
+.strength-slider,
 .smooth-slider {
     margin-top: 7px;
 }
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index aefdeee17..5b24eb7ea 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -982,6 +982,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             10,
             1
         );
+        const strength = this.getNumber('Reference Strength', '', 1, 100, this.refStrength, (val: number) => {
+            !isNaN(val) && (this.refStrength = val);
+        });
         return (
             <div>
                 {!targetDoc.layout_isSvg && this.containsInkDoc && (
@@ -995,9 +998,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                                 align="flex-start"
                                 fillWidth
                                 toggleType={ToggleType.BUTTON}
-                                onClick={undoable(() => DrawingFillHandler.drawingToImage(targetDoc, 'fill in the details of this image'), 'createImage')}
+                                onClick={undoable(() => DrawingFillHandler.drawingToImage(targetDoc, this.refStrength, 'fill in the details of this image'), 'createImage')}
                             />
                         </div>
+                        <div className="strength-slider">{strength}</div>
                         <div className="color">
                             <Toggle
                                 text={'Color with GPT'}
@@ -1052,6 +1056,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             doc[DocData].stroke_markerScale = Number(value);
         });
     }
+    @computed get refStrength() { return Number(this.getField('drawing_refStrength') || '50'); } // prettier-ignore
+    set refStrength(value) {
+        this.selectedStrokes.forEach(doc => {
+            doc[DocData].drawing_refStrength = Number(value);
+        });
+    }
     @computed get smoothAmt() { return Number(this.getField('stroke_smoothAmount') || '5'); } // prettier-ignore
     set smoothAmt(value) {
         this.selectedStrokes.forEach(doc => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index acf72e5cb..4bccdd286 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1295,6 +1295,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         docData.drawingColored = opts.autoColor;
         docData.drawingSize = opts.size;
         docData.drawingData = gptRes;
+        docData.ai_generated = true;
         this._drawingContainer = doc;
         this.addDocument(doc);
         this._batch?.end();
@@ -1992,16 +1993,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 }),
                 icon: 'eye',
             });
-        optionItems.push({
-            description: 'Show Drawing Editor',
-            event: action(() => {
-                SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc;
-                SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
-                SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing;
-                !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10) : SmartDrawHandler.Instance.hideRegenerate();
-            }),
-            icon: 'pen-to-square',
-        });
+        this.layoutDoc.drawingData != undefined &&
+            optionItems.push({
+                description: 'Show Drawing Editor',
+                event: action(() => {
+                    SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc;
+                    SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
+                    SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing;
+                    !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10) : SmartDrawHandler.Instance.hideRegenerate();
+                }),
+                icon: 'pen-to-square',
+            });
         optionItems.push({
             description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
             event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 25e7b566f..8f6a90e61 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -40,6 +40,7 @@ import { FocusViewOptions } from './FocusViewOptions';
 import './ImageBox.scss';
 import { OpenWhere } from './OpenWhere';
 import { Upload } from '../../../server/SharedMediaTypes';
+import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
 
 export class ImageEditorData {
     // eslint-disable-next-line no-use-before-define
@@ -351,6 +352,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 }),
                 icon: 'pencil-alt',
             });
+            this.layoutDoc.ai_generated &&
+                funcs.push({
+                    description: 'Regenerate AI Image',
+                    event: action(() => {
+                        console.log('COOOORDS', this.dataDoc.width as number, this.dataDoc.y as number);
+                        !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this.dataDoc.x as number, (this.dataDoc.y as number) - 10) : SmartDrawHandler.Instance.hideRegenerate();
+                    }),
+                    icon: 'pen-to-square',
+                });
             funcs.push({
                 description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
                 event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index a39878924..2a8bc034d 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -411,7 +411,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             let finalImgURL: string = url;
             // crop the image for these brush modes to remove excess blank space around the image contents
             if (currCutType == CutMode.IN || currCutType == CutMode.DRAW_IN) {
-                const croppedData = cropImage(image, minX, maxX, minY, maxY);
+                const croppedData = cropImage(image, Math.max(minX, 0), Math.min(maxX, image.width), Math.max(minY, 0), Math.min(maxY, image.height));
                 finalImg = croppedData;
                 finalImgURL = croppedData.src;
             }
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index fe03f32a5..bb8082061 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -158,6 +158,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         docData.drawingColored = opts.autoColor;
         docData.drawingSize = opts.size;
         docData.drawingData = gptRes;
+        docData.ai_generated = true;
     });
 
     pointerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index 48e71bc9f..1a470f995 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -1,21 +1,26 @@
+import { imageUrlToBase64 } from '../../../ClientUtils';
 import { Doc } from '../../../fields/Doc';
 import { ImageCast } from '../../../fields/Types';
 import { Upload } from '../../../server/SharedMediaTypes';
+import { gptDescribeImage } from '../../apis/gpt/GPT';
 import { Docs } from '../../documents/Documents';
 import { Networking } from '../../Network';
 import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
 import { OpenWhere } from '../nodes/OpenWhere';
 
 export class DrawingFillHandler {
-    static drawingToImage = (drawing: Doc, prompt: string) =>
+    static drawingToImage = (drawing: Doc, strength: number, prompt: string) =>
         DocumentView.GetDocImage(drawing)?.then(imageField => {
             if (imageField) {
                 const { href } = ImageCast(imageField).url;
                 const hrefParts = href.split('.');
                 const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
-                const strength: number = 100;
-                Networking.PostToServer('/queryFireflyImageFromStructure', { prompt, structureUrl, strength }).then((info: Upload.ImageInformation) => 
+                imageUrlToBase64(structureUrl)
+                    .then((hrefBase64: string) => gptDescribeImage(hrefBase64))
+                    .then((prompt: string) => {
+                        Networking.PostToServer('/queryFireflyImageFromStructure', { prompt, structureUrl, strength }).then((info: Upload.ImageInformation) => 
                                 DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {}), OpenWhere.addRight)) // prettier-ignore
+                    });
             }
             return false;
         });
diff --git a/src/client/views/smartdraw/SmartDrawHandler.scss b/src/client/views/smartdraw/SmartDrawHandler.scss
index c25273876..513779512 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.scss
+++ b/src/client/views/smartdraw/SmartDrawHandler.scss
@@ -12,7 +12,13 @@
         }
     }
 
-    .smartdraw-options {
+    .smartdraw-output-options {
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+    }
+
+    .smartdraw-svg-options {
         margin-top: 5px;
         display: flex;
         flex-direction: row;
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx
index 036ac5983..fb1a5771e 100644
--- a/src/client/views/smartdraw/SmartDrawHandler.tsx
+++ b/src/client/views/smartdraw/SmartDrawHandler.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Slider, Switch } from '@mui/material';
+import { Checkbox, Slider, Switch } from '@mui/material';
 import { Button, IconButton } from 'browndash-components';
 import { action, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
@@ -74,6 +74,8 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
     @observable private _autoColor: boolean = true;
     @observable private _regenInput: string = '';
     @observable private _canInteract: boolean = true;
+    @observable private _generateDrawing: boolean = true;
+    @observable private _generateImage: boolean = true;
 
     @observable public ShowRegenerate: boolean = false;
 
@@ -195,6 +197,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
      */
     @action
     handleSendClick = async () => {
+        if (!this._generateImage && !this._generateDrawing) return;
         this._isLoading = true;
         this._canInteract = false;
         if (this.ShowRegenerate) {
@@ -212,7 +215,12 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                 this._showOptions = false;
             });
             try {
-                await this.drawWithGPT({ X: this._pageX, Y: this._pageY }, this._userInput, this._complexity, this._size, this._autoColor);
+                if (this._generateImage) {
+                    await this.createImageWithFirefly(this._userInput);
+                }
+                if (this._generateDrawing) {
+                    await this.drawWithGPT({ X: this._pageX, Y: this._pageY }, this._userInput, this._complexity, this._size, this._autoColor);
+                }
                 this.hideSmartDrawHandler();
 
                 runInAction(() => {
@@ -240,15 +248,12 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
     drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => {
         if (input) {
             this._lastInput = { text: input, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y };
-
-            Networking.PostToServer('/queryFireflyImage', { prompt: input }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img.accessPaths.agnostic.client, { title: input }), OpenWhere.addRight));
-
             const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true);
             if (res) {
                 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);
-
+                this._selectedDoc = drawingDoc;
                 this._errorOccurredOnce = false;
                 return strokeData;
             } else {
@@ -258,6 +263,23 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         return undefined;
     };
 
+    /**
+     * Calls Firefly API to create an image based on user input
+     */
+    createImageWithFirefly = (input: string, seed?: number) => {
+        this._lastInput.text = input;
+        return Networking.PostToServer('/queryFireflyImage', { prompt: input, seed: seed }).then(img => {
+            const imgDoc: Doc = Docs.Create.ImageDocument(img.accessPaths.agnostic.client, {
+                title: input.match(/^(.*?)~~~.*$/)?.[1] || input,
+                ai_generated: true,
+                firefly_seed: img.accessPaths.agnostic.client.match(/\/(\d+)upload/)[1],
+                firefly_prompt: input,
+            });
+            DocumentViewInternal.addDocTabFunc(imgDoc, OpenWhere.addRight);
+            this._selectedDoc = imgDoc;
+        });
+    };
+
     /**
      * Regenerates drawings with the option to add a specific regenerate prompt/request.
      */
@@ -266,27 +288,39 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
         if (lastInput) this._lastInput = lastInput;
         if (lastResponse) this._lastResponse = lastResponse;
         if (regenInput) this._regenInput = regenInput;
-
-        try {
-            let res;
+        if (this._generateDrawing) {
+            try {
+                let res;
+                if (this._regenInput !== '') {
+                    const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`;
+                    res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true);
+                    this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`;
+                } else {
+                    res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true);
+                }
+                if (!res) {
+                    console.error('GPT call failed');
+                    return;
+                }
+                const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor);
+                this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc);
+                const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
+                drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
+            } catch (err) {
+                console.error('Error regenerating drawing', err);
+            }
+        }
+        if (this._generateImage) {
             if (this._regenInput !== '') {
-                const prompt: string = `This is your previously generated svg code: ${this._lastResponse} for the user input "${this._lastInput.text}". Please regenerate it with the provided specifications.`;
-                res = await gptAPICall(`"${this._regenInput}"`, GPTCallType.DRAW, prompt, true);
-                this._lastInput.text = `${this._lastInput.text} ~~~ ${this._regenInput}`;
+                if (this._selectedDoc) {
+                    const docData = this._selectedDoc[DocData];
+                    const newPrompt = `${docData.firefly_prompt}, ${this._regenInput}`;
+                    const seed: number = docData?.firefly_seed as number;
+                    await this.createImageWithFirefly(newPrompt, seed);
+                }
             } else {
-                res = await gptAPICall(`"${this._lastInput.text}", "${this._lastInput.complexity}", "${this._lastInput.size}"`, GPTCallType.DRAW, undefined, true);
+                await this.createImageWithFirefly(this._lastInput.text);
             }
-            if (!res) {
-                console.error('GPT call failed');
-                return;
-            }
-            const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor);
-            this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc);
-            const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes);
-            drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res);
-            return strokeData;
-        } catch (err) {
-            console.error('Error regenerating drawing', err);
         }
     };
 
@@ -397,58 +431,87 @@ export class SmartDrawHandler extends ObservableReactComponent<object> {
                 </div>
                 {this._showOptions && (
                     <div>
-                        <div className="smartdraw-options">
-                            <div className="auto-color">
-                                Auto color
-                                <Switch
-                                    sx={{
-                                        '& .MuiSwitch-switchBase.Mui-checked': { color: SettingsManager.userColor },
-                                        '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: SettingsManager.userVariantColor },
-                                    }}
-                                    defaultChecked={true}
-                                    value={this._autoColor}
-                                    size="small"
-                                    onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))}
-                                />
-                            </div>
-                            <div className="complexity">
-                                Complexity
-                                <Slider
+                        <div className="smartdraw-output-options">
+                            <div className="drawing-checkbox">
+                                Generate Ink
+                                <Checkbox
                                     sx={{
-                                        '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
-                                        '& .MuiSlider-rail': { color: SettingsManager.userColor },
-                                        '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
+                                        color: 'white',
+                                        '&.Mui-checked': {
+                                            color: SettingsManager.userVariantColor,
+                                        },
                                     }}
-                                    style={{ width: '80%' }}
-                                    min={1}
-                                    max={10}
-                                    step={1}
-                                    size="small"
-                                    value={this._complexity}
-                                    onChange={action((e, val) => this._canInteract && (this._complexity = val as number))}
-                                    valueLabelDisplay="auto"
+                                    checked={this._generateDrawing}
+                                    onChange={() => this._canInteract && (this._generateDrawing = !this._generateDrawing)}
                                 />
                             </div>
-                            <div className="size">
-                                Size (in pixels)
-                                <Slider
-                                    className="size-slider"
+                            <div className="image-checkbox">
+                                Generate Image
+                                <Checkbox
                                     sx={{
-                                        '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
-                                        '& .MuiSlider-rail': { color: SettingsManager.userColor },
-                                        '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20` } },
+                                        color: 'white',
+                                        '&.Mui-checked': {
+                                            color: SettingsManager.userVariantColor,
+                                        },
                                     }}
-                                    min={50}
-                                    max={700}
-                                    step={10}
-                                    size="small"
-                                    value={this._size}
-                                    onChange={action((e, val) => this._canInteract && (this._size = val as number))}
-                                    valueLabelDisplay="auto"
+                                    checked={this._generateImage}
+                                    onChange={() => this._canInteract && (this._generateImage = !this._generateImage)}
                                 />
                             </div>
                         </div>
-                        <div></div>
+                        {this._generateDrawing && (
+                            <div className="smartdraw-svg-options">
+                                <div className="auto-color">
+                                    Auto color
+                                    <Switch
+                                        sx={{
+                                            '& .MuiSwitch-switchBase.Mui-checked': { color: SettingsManager.userColor },
+                                            '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: SettingsManager.userVariantColor },
+                                        }}
+                                        defaultChecked={true}
+                                        value={this._autoColor}
+                                        size="small"
+                                        onChange={action(() => this._canInteract && (this._autoColor = !this._autoColor))}
+                                    />
+                                </div>
+                                <div className="complexity">
+                                    Complexity
+                                    <Slider
+                                        sx={{
+                                            '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
+                                            '& .MuiSlider-rail': { color: SettingsManager.userColor },
+                                            '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
+                                        }}
+                                        style={{ width: '80%' }}
+                                        min={1}
+                                        max={10}
+                                        step={1}
+                                        size="small"
+                                        value={this._complexity}
+                                        onChange={action((e, val) => this._canInteract && (this._complexity = val as number))}
+                                        valueLabelDisplay="auto"
+                                    />
+                                </div>
+                                <div className="size">
+                                    Size (in pixels)
+                                    <Slider
+                                        className="size-slider"
+                                        sx={{
+                                            '& .MuiSlider-track': { color: SettingsManager.userVariantColor },
+                                            '& .MuiSlider-rail': { color: SettingsManager.userColor },
+                                            '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20` } },
+                                        }}
+                                        min={50}
+                                        max={700}
+                                        step={10}
+                                        size="small"
+                                        value={this._size}
+                                        onChange={action((e, val) => this._canInteract && (this._size = val as number))}
+                                        valueLabelDisplay="auto"
+                                    />
+                                </div>
+                            </div>
+                        )}
                     </div>
                 )}
             </div>
diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx
index d56878f10..352a02e32 100644
--- a/src/client/views/smartdraw/StickerPalette.tsx
+++ b/src/client/views/smartdraw/StickerPalette.tsx
@@ -186,6 +186,7 @@ export class StickerPalette extends ObservableReactComponent<StickerPaletteProps
         docData.drawingColored = this._opts.autoColor;
         docData.drawingSize = this._opts.size;
         docData.drawingData = this._gptRes[cIndex];
+        docData.ai_generated = true;
         focusedDrawing.width = this._opts.size;
         docData.x = this._opts.x;
         docData.y = this._opts.y;
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index e73795e41..c5348c7db 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -65,7 +65,11 @@ export default class FireflyManager extends ApiManager {
             })
         );
 
-    generateImage = (prompt: string = 'a realistic illustration of a cat coding') => {
+    generateImage = (prompt: string = 'a realistic illustration of a cat coding', seed?: number) => {
+        let body = `{  "prompt": "${prompt}" }`;
+        if (seed) {
+            body = `{  "prompt": "${prompt}", "seeds": [${seed}]}`;
+        }
         const fetched = this.getBearerToken().then(response =>
             response?.json().then((data: { access_token: string }) =>
                 fetch('https://firefly-api.adobe.io/v3/images/generate', {
@@ -76,9 +80,15 @@ export default class FireflyManager extends ApiManager {
                         ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''],
                         ['Authorization', `Bearer ${data.access_token}`],
                     ],
-                    body: `{  "prompt": "${prompt}" }`,
+                    body: body,
                 })
-                    .then(response2 => response2.json().then(json => (json.outputs?.[0] as { image: { url: string } })?.image.url))
+                    .then(response2 =>
+                        response2.json().then(json => {
+                            const seed = json.outputs?.[0]?.seed;
+                            const url = json.outputs?.[0]?.image?.url;
+                            return { seed, url };
+                        })
+                    )
                     .catch(error => {
                         console.error('Error:', error);
                         return undefined;
@@ -226,8 +236,8 @@ export default class FireflyManager extends ApiManager {
             method: Method.POST,
             subscription: '/queryFireflyImage',
             secureHandler: ({ req, res }) =>
-                this.generateImage(req.body.prompt).then(url =>
-                    DashUploadUtils.UploadImage(url ?? '').then(info => {
+                this.generateImage(req.body.prompt, req.body.seed).then(img =>
+                    DashUploadUtils.UploadImage(img?.url ?? '', undefined, img?.seed).then(info => {
                         if (info instanceof Error) _invalid(res, info.message);
                         else _success(res, info);
                     })
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 623172894..2177c5d97 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -458,7 +458,6 @@ export namespace DashUploadUtils {
             return { name: result.name, message: result.message };
         }
         const outputFile = filename || result.filename || '';
-
         return UploadInspectedImage(result, outputFile, prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false);
     };
 
-- 
cgit v1.2.3-70-g09d2


From cb11630ed4fe7d588a5055357805fb1ac972fc75 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 Jan 2025 12:17:49 -0500
Subject: minor cleanup

---
 src/client/views/PropertiesView.tsx                | 1 -
 src/client/views/nodes/ImageBox.tsx                | 4 ++--
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 9 ++-------
 3 files changed, 4 insertions(+), 10 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index ae8abea2c..54bcc02bc 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -3,7 +3,6 @@ import { faAnchor, faArrowRight, faWindowMaximize } from '@fortawesome/free-soli
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox, Tooltip } from '@mui/material';
 import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Toggle, ToggleType, Type } from '@dash/components';
-import { Property } from 'csstype';
 import { concat } from 'lodash';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e0ef8f423..778d52a8d 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -50,9 +50,9 @@ export class ImageEditorData {
     private static _instance: ImageEditorData;
     private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore
     @observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined });
-    @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => {
+    private static set = action((open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => {
         this._instance.imageData = { open, rootDoc, source, addDoc };
-    };
+    });
 
     constructor() {
         makeObservable(this);
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 809ced2f6..2ae6ee1dd 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -1,5 +1,4 @@
 /* eslint-disable no-use-before-define */
-/* eslint-disable react/function-component-definition */
 import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material';
 import { Button, IconButton, Type } from '@dash/components';
 import * as React from 'react';
@@ -311,7 +310,6 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             }
             if (!(originalImg.current && imageRootDoc)) return;
             // add the doc to the main freeform
-            // eslint-disable-next-line no-use-before-define
             await createNewImgDoc(originalImg.current, true);
             originalImg.current = currImg.current;
             originalDoc.current = parentDoc.current;
@@ -320,14 +318,12 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                 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;
@@ -751,10 +747,9 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
     function renderEditThumbnails() {
         return (
             <div className="editsBox">
-                {edits.map((edit, i) => (
+                {edits.map(edit => (
                     <img
-                        // eslint-disable-next-line react/no-array-index-key
-                        key={i}
+                        key={edit.url}
                         alt="image edits"
                         width={75}
                         src={edit.url}
-- 
cgit v1.2.3-70-g09d2


From a496c583f375fe5e118f786b1244c42bc8a34fec Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 28 Jan 2025 14:20:40 -0500
Subject: fixed flashcard practice mode.  moved doc sorting from cardDeck into
 collectionSubView to apply it to all collections.

---
 .../src/components/Dropdown/Dropdown.tsx           |  30 ++----
 src/client/util/CurrentUserUtils.ts                |  10 +-
 .../views/collections/CollectionCardDeckView.tsx   | 119 ++++-----------------
 src/client/views/collections/CollectionSubView.tsx |  49 ++++++++-
 .../views/collections/FlashcardPracticeUI.tsx      |   4 +-
 src/client/views/global/globalScripts.ts           |  35 +++---
 src/client/views/nodes/ComparisonBox.tsx           |   2 +-
 src/client/views/nodes/imageEditor/ImageEditor.tsx |  69 ++----------
 .../views/nodes/imageEditor/ImageEditorButtons.tsx |   1 +
 9 files changed, 108 insertions(+), 211 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/packages/components/src/components/Dropdown/Dropdown.tsx b/packages/components/src/components/Dropdown/Dropdown.tsx
index 0953f412c..b9b6f01b8 100644
--- a/packages/components/src/components/Dropdown/Dropdown.tsx
+++ b/packages/components/src/components/Dropdown/Dropdown.tsx
@@ -99,22 +99,14 @@ export const Dropdown = (props: IDropdownProps) => {
         background: background ?? color,
     };
 
-    const getCaretDirection = (active: boolean, placement: Placement = 'left'): JSX.Element => {
-        if (iconProvider) return iconProvider(active, placement);
-        switch (placement) {
-            case 'bottom':
-                if (active) return <FaCaretUp />;
-                return <FaCaretDown />;
-            case 'right':
-                if (active) return <FaCaretLeft />;
-                return <FaCaretRight />;
-            case 'top':
-                if (active) return <FaCaretDown />;
-                return <FaCaretUp />;
-            default:
-                if (active) return <FaCaretUp />;
-                return <FaCaretDown />;
-        }
+    const getCaretDirection = (isActive: boolean, caretPlacement: Placement = 'left'): JSX.Element => {
+        if (iconProvider) return iconProvider(isActive, caretPlacement);
+        switch (caretPlacement) {
+            default:      
+            case 'bottom':return isActive ?  <FaCaretUp />: <FaCaretDown />;
+            case 'right': return isActive ? <FaCaretLeft /> : <FaCaretRight />;
+            case 'top':   return isActive ? <FaCaretDown />: <FaCaretUp />;
+        } // prettier-ignore
     };
 
     const getToggle = () => {
@@ -143,9 +135,9 @@ export const Dropdown = (props: IDropdownProps) => {
         }
     };
 
-    const setActiveChanged = (active: boolean) => {
-        setActive(active);
-        props.activeChanged?.(active);
+    const setActiveChanged = (isActive: boolean) => {
+        setActive(isActive);
+        props.activeChanged?.(isActive);
     };
 
     const dropdown: JSX.Element = (
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7c36a82f2..e806675aa 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -688,7 +688,7 @@ pie title Minerals in my tap water
             { title: "Center",    icon: "align-center",  toolTip: "Center Align Stack",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"hcenter",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
         ]
     }
-    static cardTools(): Button[] {
+    static sortTools(): Button[] {
         return [
             { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",          btnType: ButtonType.ToggleButton, expertMode: false, toolType:"time",       funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                          btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
@@ -704,7 +704,7 @@ pie title Minerals in my tap water
          ]
     }
   
-    static tagGroupTools(): Button[] {
+    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_);}'}},
@@ -723,7 +723,6 @@ pie title Minerals in my tap water
             { title: "Grid",      icon: "border-all",          toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Fit All",   icon: "object-group",        toolTip: "Fit Docs to View (double click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform
             { title: "Clusters",  icon: "braille",             toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
-            { title: "Cards",     icon: "brain",               toolTip: "Flashcards",          btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
         ]
     }
     static textTools():Button[] {
@@ -822,15 +821,14 @@ pie title Minerals in my tap water
             { title: "Num",     icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'},                  width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             
-            { title: "Filter",  icon: "=",      toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.tagGroupTools(),ignoreClick:true, toolType:DocumentType.COL,            funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string},
+            { title: "Filter",  icon: "=",      toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.filterTools(), ignoreClick:true,  toolType:DocumentType.COL,            funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string},
+            { title: "Sort",    icon: "Sort",   toolTip: "Sort Documents",       subMenu: CurrentUserUtils.sortTools(),   expertMode: false, toolType:DocumentType.COL,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Text",    icon: "Text",   toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Ink",     icon: "Ink",    toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {hidden: `IsExploreMode()`,                                                   linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
             { title: "Doc",     icon: "Doc",    toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "View",    icon: "View",   toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Stack",   icon: "View",   toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             
-            { title: "Card",    icon: "Card",   toolTip: "Card View Tools",      subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
-         // { title: "Create",  icon: "Create", toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",    toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
             { title: "Video",   icon: "Video",  toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
             { title: "Image",   icon: "Image",  toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 1bca68846..aaf33c3a1 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,16 +1,16 @@
-import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import * as React from 'react';
 import * as CSS from 'csstype';
-import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
+import { ClientUtils, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
-import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Animation, DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, DateCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { URLField } from '../../../fields/URLField';
 import { gptImageLabel } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
@@ -25,21 +25,12 @@ import { StyleProp } from '../StyleProp';
 import { TagItem } from '../TagsView';
 import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
-import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
+import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { SettingsManager } from '../../util/SettingsManager';
 
-enum cardSortings {
-    Time = 'time',
-    Type = 'type',
-    Color = 'color',
-    Chat = 'chat',
-    Tag = 'tag',
-    None = '',
-}
-
 /**
  * New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily
  * sort and filter using presets, and customize your experience with chat gpt.
@@ -60,7 +51,6 @@ export class CollectionCardView extends CollectionSubView() {
     @observable _forceChildXf = 0;
     @observable _hoveredNodeIndex = -1;
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
-    @observable _docDraggedIndex: number = -1;
     @observable _cursor: CSS.Property.Cursor = 'ew-resize';
 
     constructor(props: SubCollectionViewProps) {
@@ -102,18 +92,8 @@ export class CollectionCardView extends CollectionSubView() {
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        this._disposers.sort = reaction(
-            () => GPTPopup.Instance.visible,
-            isVis => {
-                if (isVis) {
-                    this.openChatPopup();
-                } else {
-                    this.Document.card_sort = this.cardSort === cardSortings.Chat ? '' : this.Document.card_sort;
-                }
-            }
-        );
         // if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles
-        // when inquired from the dom (below in childScreenToLocal).  When the doc is actually renders, we need to act like the
+        // when inquired from the dom (below in childScreenToLocal).  When the doc is actually rendered, we need to act like the
         // dash data just changed and trigger a React involidation with the correct data (read from the dom).
         this._disposers.child = reaction(
             () => [this.Document.x, this.Document.y],
@@ -136,14 +116,11 @@ export class CollectionCardView extends CollectionSubView() {
         this._dropDisposer?.();
     }
 
-    @computed get cardSort() {
-        return StrCast(this.Document.card_sort) as cardSortings;
-    }
     /**
      * Number of rows of cards to be rendered
      */
     @computed get numRows() {
-        return Math.ceil(this.sortedDocs.length / this._maxRowCount);
+        return Math.ceil(this.childDocs.length / this._maxRowCount);
     }
     /**
      * Circle arc size, in radians, to layout cards
@@ -211,7 +188,7 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns the card's new index
      */
     findCardDropIndex = (mouseX: number, mouseY: number) => {
-        const cardCount = this.sortedDocs.length;
+        const cardCount = this.childDocs.length;
         let index = 0;
         const cardWidth = cardCount < this._maxRowCount ? this._props.PanelWidth() / cardCount : this._props.PanelWidth() / this._maxRowCount;
 
@@ -245,8 +222,8 @@ export class CollectionCardView extends CollectionSubView() {
      */
     @action
     onPointerMove = (x: number, y: number) => {
-        if (DragManager.docsBeingDragged.some(doc => this.sortedDocs.includes(doc)) || SnappingManager.CanEmbed) {
-            this._docDraggedIndex = this.findCardDropIndex(x, y);
+        if (DragManager.docsBeingDragged.some(doc => this.childDocs.includes(doc)) || SnappingManager.CanEmbed) {
+            this.docDraggedIndex = this.findCardDropIndex(x, y);
         }
     };
 
@@ -259,11 +236,11 @@ export class CollectionCardView extends CollectionSubView() {
     onInternalDrop = undoable(
         action((e: Event, de: DragManager.DropEvent) => {
             if (de.complete.docDragData) {
-                const dragIndex = this._docDraggedIndex;
+                const dragIndex = this.docDraggedIndex;
                 const draggedDoc = DragManager.docsBeingDragged[0];
                 if (dragIndex > -1 && draggedDoc) {
-                    this._docDraggedIndex = -1;
-                    const sorted = this.sortedDocs;
+                    this.docDraggedIndex = -1;
+                    const sorted = this.childDocs;
                     const originalIndex = sorted.findIndex(doc => doc === draggedDoc);
 
                     this.Document.card_sort = '';
@@ -295,50 +272,6 @@ export class CollectionCardView extends CollectionSubView() {
             .map(({ i }) => i)
             .join('.');
 
-    /**
-     * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
-     * front, latter cards to the back
-     * @param docs
-     * @param sortType
-     * @param isDesc
-     * @returns
-     */
-    sort = (docsIn: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
-        const docs = docsIn.slice(); // need make new object list since sort() modifies the incoming list which confuses mobx caching
-        sortType &&
-            docs.sort((docA, docB) => {
-                const [typeA, typeB] = (() => {
-                    switch (sortType) {
-                        default:                
-                        case cardSortings.Type: return [StrCast(docA.type),                                   StrCast(docB.type)];
-                        case cardSortings.Chat: return [NumCast(docA.chatIndex, 9999),                        NumCast(docB.chatIndex,9999)];
-                        case cardSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(),       DateCast(docB.author_date)?.date ?? Date.now()];
-                        case cardSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()];
-                        case cardSortings.Tag:  return [StrListCast(docA.tags).join(""),                      StrListCast(docB.tags).join("")];
-                    }
-                })(); //prettier-ignore
-                return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1);
-            });
-        if (dragIndex !== -1) {
-            const draggedDoc = DragManager.docsBeingDragged[0];
-            const originalIndex = docs.findIndex(doc => doc === draggedDoc);
-
-            originalIndex !== -1 && docs.splice(originalIndex, 1);
-            draggedDoc && docs.splice(dragIndex, 0, draggedDoc);
-        }
-
-        return docs;
-    };
-
-    @computed get sortedDocs() {
-        return this.sort(
-            this.childCards.map(card => card.layout),
-            this.cardSort,
-            BoolCast(this.Document.card_sort_isDesc),
-            this._docDraggedIndex
-        );
-    }
-
     isChildContentActive = computedFn(
         (doc: Doc) => () =>
             this._props.isContentActive?.() === false
@@ -560,16 +493,6 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }, '');
 
-    /**
-     * Opens up the chat popup and starts the process for smart sorting.
-     */
-    openChatPopup = async () => {
-        GPTPopup.Instance.setVisible(true);
-        GPTPopup.Instance.setMode(GPTPopupMode.CARD);
-        GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded
-        await this.childPairStringListAndUpdateSortDesc();
-    };
-
     childScreenToLocal = computedFn((doc: Doc, index: number, isSelected: boolean) => () => {
         // need to explicitly trigger an invalidation since we're reading everything from the Dom
         this._forceChildXf;
@@ -615,7 +538,7 @@ export class CollectionCardView extends CollectionSubView() {
         setupMoveUpEvents(
             this,
             e,
-            (e: PointerEvent, down: number[], delta: number[]) => {
+            (emove: PointerEvent, down: number[], delta: number[]) => {
                 this.layoutDoc._cardWidth = Math.max(10, delta[0] < 0 ? Math.floor(this.cardWidth + delta[0]) : Math.ceil(this.cardWidth + delta[0]));
                 return false;
             },
@@ -662,9 +585,9 @@ export class CollectionCardView extends CollectionSubView() {
      * Actually renders all the cards
      */
     @computed get renderCards() {
-        trace();
+        console.log(this.docDraggedIndex, this.childDocs[0].title, this.childDocs[1].title);
         // Map sorted documents to their rendered components
-        return this.sortedDocs.map((doc, index) => {
+        return this.childDocs.map((doc, index) => {
             const cardsInRow = this.cardsInRowThatIncludesCardIndex(index);
 
             const childScreenToLocal = this.childScreenToLocal(doc, index, doc === this.curDoc());
@@ -674,7 +597,7 @@ export class CollectionCardView extends CollectionSubView() {
             const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
             const vscale = Math.max(1,Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale * this.nativeScaling) / (aspect * this.childPanelWidth()), 
                                     (this._props.PanelHeight() - 80) / (aspect * (this._props.PanelWidth() / 10)))); // prettier-ignore
-            const hscale = Math.min(this.sortedDocs.length, this._maxRowCount) / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size
+            const hscale = Math.min(this.childDocs.length, this._maxRowCount) / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size
             return (
                 <div
                     key={doc[Id]}
@@ -710,17 +633,13 @@ export class CollectionCardView extends CollectionSubView() {
     curDoc = () => DocCast(this.layoutDoc._card_curDoc);
 
     render() {
-        trace();
         const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale;
         return (
             <div
                 className="collectionCardView-outer"
                 ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
-                onPointerDown={action(e => {
-                    if (e.button === 2 || e.ctrlKey) return;
-                    this.releaseCurDoc();
-                })}
-                onPointerLeave={action(() => (this._docDraggedIndex = -1))}
+                onPointerDown={e => e.button !== 2 && !e.ctrlKey && this.releaseCurDoc()}
+                onPointerLeave={action(() => (this.docDraggedIndex = -1))}
                 onPointerMove={e => this.onPointerMove(...this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY))}
                 onDrop={this.onExternalDrop.bind(this)}
                 style={{
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 0c059f729..5e99bec39 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, makeObservable, observable } from 'mobx';
 import * as React from 'react';
 import * as rp from 'request-promise';
-import { ClientUtils, returnFalse } from '../../../ClientUtils';
+import { ClientUtils, DashColor, returnFalse } from '../../../ClientUtils';
 import CursorField from '../../../fields/CursorField';
 import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc';
 import { AclPrivate, DocData } from '../../../fields/DocSymbols';
@@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -29,6 +29,14 @@ import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FlashcardPracticeUI } from './FlashcardPracticeUI';
 import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere';
 
+export enum docSortings {
+    Time = 'time',
+    Type = 'type',
+    Color = 'color',
+    Chat = 'chat',
+    Tag = 'tag',
+    None = '',
+}
 export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> {
     isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc)
     isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
@@ -150,6 +158,8 @@ export function CollectionSubView<X>() {
         unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !ClientUtils.IsRecursiveFilter(f)) || [])];
         childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()];
         searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs);
+
+        @observable docDraggedIndex = -1;
         @computed.struct get childDocs() {
             TraceMobx();
             let rawdocs: (Doc | Promise<Doc>)[] = [];
@@ -166,8 +176,10 @@ export function CollectionSubView<X>() {
                 const templateRoot = this._props.TemplateDataDocument;
                 rawdocs = templateRoot && !this._props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : [];
             }
-
-            const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc);
+            const childDocs = this.childSortedDocs(
+                rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc),
+                this.docDraggedIndex
+            );
 
             const childDocFilters = this.childDocFilters();
             const childFiltersByRanges = this.childDocRangeFilters();
@@ -214,6 +226,35 @@ export function CollectionSubView<X>() {
             return docsforFilter;
         }
 
+        childSortedDocs = (docsIn: Doc[], dragIndex: number) => {
+            const sortType = StrCast(this.Document[this._props.fieldKey + '_sort']) as docSortings;
+            const isDesc = BoolCast(this.Document[this._props.fieldKey + '_sort_desc']);
+            const docs = docsIn.slice();
+            if (sortType) {
+                docs.sort((docA, docB) => {
+                    const [typeA, typeB] = (() => {
+                        switch (sortType) {
+                            default:                
+                            case docSortings.Type: return [StrCast(docA.type),                                   StrCast(docB.type)];
+                            case docSortings.Chat: return [NumCast(docA.chatIndex, 9999),                        NumCast(docB.chatIndex,9999)];
+                            case docSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(),       DateCast(docB.author_date)?.date ?? Date.now()];
+                            case docSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()];
+                            case docSortings.Tag:  return [StrListCast(docA.tags).join(""),                      StrListCast(docB.tags).join("")];
+                        }
+                    })(); //prettier-ignore
+                    return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1);
+                });
+            }
+            if (dragIndex !== -1) {
+                const draggedDoc = DragManager.docsBeingDragged[0];
+                const originalIndex = docs.findIndex(doc => doc === draggedDoc);
+
+                originalIndex !== -1 && docs.splice(originalIndex, 1);
+                draggedDoc && docs.splice(dragIndex, 0, draggedDoc);
+            }
+            return docs;
+        };
+
         @action
         protected async setCursorPosition(position: [number, number]) {
             let ind;
diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx
index 77f1db9ad..c071c5fb8 100644
--- a/src/client/views/collections/FlashcardPracticeUI.tsx
+++ b/src/client/views/collections/FlashcardPracticeUI.tsx
@@ -58,7 +58,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
     get practiceField() { return this._props.fieldKey + "_practice"; } // prettier-ignore
 
     @computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter'); } // prettier-ignore
-    @computed get practiceMode() {  return this._props.allChildDocs().some(doc => doc._flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore
+    @computed get practiceMode() {  return this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore
 
     btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale);
     btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height));
@@ -179,7 +179,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp
             </div>
         );
     }
-    tryFilterOut = (doc: Doc) => (this.practiceMode && BoolCast(doc?._flashcardType) && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct
+    tryFilterOut = (doc: Doc) => (this.practiceMode && doc?._layout_flashcardType && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct
     render() {
         return (
             <div className="FlashcardPracticeUI">
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 542417531..b02eabab0 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -40,6 +40,7 @@ import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import { OpenWhere } from '../nodes/OpenWhere';
+import { docSortings } from '../collections/CollectionSubView';
 
 // eslint-disable-next-line prefer-arrow-callback
 ScriptingGlobals.add(function IsNoneSelected() {
@@ -197,43 +198,35 @@ ScriptingGlobals.add(function showFreeform(
             checkResult: (doc: Doc) => BoolCast(doc?._freeform_useClusters, false),
             setDoc: (doc: Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; },
         }],
-        ['flashcards', {
-            checkResult: (doc: Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
-            setDoc: (doc: Doc, dv: DocumentView) => { Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards}, // prettier-ignore
-        }],
         ['time', {
-            checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "time",
-            setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "time" ? doc.card_sort = '' : doc.card_sort = 'time'}, // prettier-ignore
+            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "time",
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore
         }],
         ['docType', {
-            checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "type",
-            setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "type" ? doc.card_sort = '' : doc.card_sort = 'type'}, // prettier-ignore
+            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "type",
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore
         }],
         ['color', {
-            checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "color",
-            setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "color" ? doc.card_sort = '' : doc.card_sort = 'color'}, // prettier-ignore
+            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "color",
+            setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutFieldKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore
         }],
         ['tag', {
-            checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "tag",
-            setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "tag" ? doc.card_sort = '' : doc.card_sort = 'tag'}, // prettier-ignore
+            checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "tag",
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore
         }],
         ['up', {
-            checkResult: (doc: Doc) => BoolCast(!doc?.card_sort_isDesc),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                doc.card_sort_isDesc = false;
-            },
+            checkResult: (doc: Doc) => BoolCast(!doc?.[Doc.LayoutFieldKey(doc)+"_sort_desc"]),
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = undefined; },
         }],
         ['down', {
-            checkResult: (doc: Doc) => BoolCast(doc?.card_sort_isDesc),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                doc.card_sort_isDesc = true;
-            },
+            checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort_desc"]),
+            setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = true; },
         }],
         ['toggle-chat', {
             checkResult: (doc: Doc) => GPTPopup.Instance.visible,
             setDoc: (doc: Doc, dv: DocumentView) => {
                 if (GPTPopup.Instance.visible){
-                    doc.card_sort = ''
+                    doc[Doc.LayoutFieldKey(doc)+"_sort"] = '';
                     GPTPopup.Instance.setVisible(false);
    
                 } else {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index e0c360132..f5291a4c1 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import axios from 'axios';
-import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import ReactLoading from 'react-loading';
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 2ae6ee1dd..6b1d05031 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -551,8 +551,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         img.src = src;
         if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined;
         try {
-            const res = await createNewImgDoc(img, false);
-            return res;
+            return await createNewImgDoc(img, false);
         } catch (err) {
             console.log(err);
         }
@@ -589,9 +588,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                                 // disable once edited has been clicked (doesn't make sense to change after first edit)
                                 disabled={edited}
                                 checked={isNewCollection}
-                                onChange={() => {
-                                    setIsNewCollection(prev => !prev);
-                                }}
+                                onChange={() => setIsNewCollection(prev => !prev)}
                             />
                         }
                         label="Create New Collection"
@@ -610,49 +607,13 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         return (
             <div className="sideControlsContainer" style={{ backgroundColor: bgColor }}>
                 <div className="sideControls">
-                    <div className="imageToolsContainer">
-                        {imageEditTools.map(tool => {
-                            return ImageToolButton(tool, tool.type === currTool.type, changeTool);
-                        })}
-                    </div>
+                    <div className="imageToolsContainer">{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool.type, changeTool))}</div>
                     {currTool.type == ImageToolType.Cut && (
                         <div className="cutToolsContainer">
-                            <Button
-                                style={{ width: '100%' }}
-                                text="Keep in"
-                                type={Type.TERT}
-                                color={cutType == CutMode.IN ? SettingsManager.userColor : bgColor}
-                                onClick={() => {
-                                    setCutType(CutMode.IN);
-                                }}
-                            />
-                            <Button
-                                style={{ width: '100%' }}
-                                text="Keep out"
-                                type={Type.TERT}
-                                color={cutType == CutMode.OUT ? SettingsManager.userColor : bgColor}
-                                onClick={() => {
-                                    setCutType(CutMode.OUT);
-                                }}
-                            />
-                            <Button
-                                style={{ width: '100%' }}
-                                text="Draw in"
-                                type={Type.TERT}
-                                color={cutType == CutMode.DRAW_IN ? SettingsManager.userColor : bgColor}
-                                onClick={() => {
-                                    setCutType(CutMode.DRAW_IN);
-                                }}
-                            />
-                            <Button
-                                style={{ width: '100%' }}
-                                text="Erase"
-                                type={Type.TERT}
-                                color={cutType == CutMode.ERASE ? SettingsManager.userColor : bgColor}
-                                onClick={() => {
-                                    setCutType(CutMode.ERASE);
-                                }}
-                            />
+                            <Button style={{ width: '100%' }} text="Keep in" type={Type.TERT} color={cutType == CutMode.IN ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.IN)} />
+                            <Button style={{ width: '100%' }} text="Keep out" type={Type.TERT} color={cutType == CutMode.OUT ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.OUT)} />
+                            <Button style={{ width: '100%' }} text="Draw in" type={Type.TERT} color={cutType == CutMode.DRAW_IN ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.DRAW_IN)} />
+                            <Button style={{ width: '100%' }} text="Erase" type={Type.TERT} color={cutType == CutMode.ERASE ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.ERASE)} />
                         </div>
                     )}
                     <div className="sliderContainer" onPointerDown={e => e.stopPropagation()}>
@@ -669,9 +630,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                                 defaultValue={genFillTool.sliderDefault}
                                 size="small"
                                 valueLabelDisplay="auto"
-                                onChange={(e, val) => {
-                                    setCursorData(prev => ({ ...prev, width: val as number }));
-                                }}
+                                onChange={(e, val) => setCursorData(prev => ({ ...prev, width: val as number }))}
                             />
                         )}
                         {currTool.type === ImageToolType.Cut && (
@@ -687,9 +646,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                                 defaultValue={cutTool.sliderDefault}
                                 size="small"
                                 valueLabelDisplay="auto"
-                                onChange={(e, val) => {
-                                    setCursorData(prev => ({ ...prev, width: val as number }));
-                                }}
+                                onChange={(e, val) => setCursorData(prev => ({ ...prev, width: val as number }))}
                             />
                         )}
                     </div>
@@ -701,9 +658,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                                 e.stopPropagation();
                                 handleUndo();
                             }}
-                            onPointerUp={e => {
-                                e.stopPropagation();
-                            }}
+                            onPointerUp={e => e.stopPropagation()}
                             color={activeColor}
                             tooltip="Undo"
                             icon={<IoMdUndo />}
@@ -714,9 +669,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                                 e.stopPropagation();
                                 handleRedo();
                             }}
-                            onPointerUp={e => {
-                                e.stopPropagation();
-                            }}
+                            onPointerUp={e => e.stopPropagation()}
                             color={activeColor}
                             tooltip="Redo"
                             icon={<IoMdRedo />}
diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
index de2116253..e810881a5 100644
--- a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
@@ -55,6 +55,7 @@ export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTo
     return (
         <div className="imageEditorButtonContainer">
             <Button
+                key={tool.name}
                 style={{ width: '100%' }}
                 text={tool.name}
                 type={Type.TERT}
-- 
cgit v1.2.3-70-g09d2


From 30ec70722678eba318fb7521a8b2bd642328bf54 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 23 Feb 2025 16:34:40 -0500
Subject: added option+maximize to open in doc editor view (currently only for
 images).  fixed imageEditor to generate edits the first time through.

---
 src/client/views/DocumentDecorations.tsx           |  7 +-
 src/client/views/StyleProviderQuiz.tsx             |  2 +-
 src/client/views/ViewBoxInterface.ts               |  1 +
 src/client/views/nodes/ImageBox.tsx                | 21 +++---
 src/client/views/nodes/calendarBox/CalendarBox.tsx |  2 +-
 src/client/views/nodes/imageEditor/ImageEditor.tsx | 83 ++++++++++------------
 .../views/nodes/imageEditor/ImageEditorButtons.tsx |  4 +-
 .../imageEditor/imageEditorUtils/ImageHandler.ts   |  1 -
 .../imageEditorUtils/imageEditorInterfaces.ts      |  5 +-
 9 files changed, 58 insertions(+), 68 deletions(-)

(limited to 'src/client/views/nodes/imageEditor/ImageEditor.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 54ff3904d..d9b6bdf1a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -36,6 +36,7 @@ import { ImageBox } from './nodes/ImageBox';
 import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { TagsView } from './TagsView';
+import { ImageField } from '../../fields/URLField';
 
 interface DocumentDecorationsProps {
     PanelWidth: number;
@@ -284,8 +285,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                 embedding.y = -NumCast(embedding._height) / 2;
                 CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right);
             } else if (e.altKey) {
-                // open same document in new tab
-                CollectionDockingView.ToggleSplit(selView.Document, OpenWhereMod.right);
+                // open same document in new tab or in custom editor
+                selView.ComponentView?.docEditorView?.() ?? CollectionDockingView.ToggleSplit(selView.Document, OpenWhereMod.right);
             } else {
                 let openDoc = selView.Document;
                 if (openDoc.layout_fieldKey === 'layout_icon') {
@@ -821,7 +822,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 {hideDeleteButton ? null : topBtn('close', 'times', undefined, () => this.onCloseClick(true), 'Close')}
                                 {hideResizers || hideDeleteButton ? null : topBtn('minimize', 'window-maximize', undefined, () => this.onCloseClick(undefined), 'Minimize')}
                                 {titleArea}
-                                {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
+                                {hideOpenButton ? <div /> : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection, opption: in editor view)')}
                             </div>
                             {hideResizers ? null : (
                                 <>
diff --git a/src/client/views/StyleProviderQuiz.tsx b/src/client/views/StyleProviderQuiz.tsx
index d8eeb3490..db9ab831a 100644
--- a/src/client/views/StyleProviderQuiz.tsx
+++ b/src/client/views/StyleProviderQuiz.tsx
@@ -125,7 +125,7 @@ export namespace styleProviderQuiz {
         try {
             const hrefBase64 = await createCanvas(img);
             const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this image with each question and answer labeled as "question" and "answer". Do not label each flashcard and do not include asterisks: ');
-            AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc['x']), NumCast(img.layoutDoc['y']));
+            AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc.x), NumCast(img.layoutDoc.y));
         } catch (error) {
             console.log('Error', error);
         }
diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts
index 30da8c616..b943259ff 100644
--- a/src/client/views/ViewBoxInterface.ts
+++ b/src/client/views/ViewBoxInterface.ts
@@ -23,6 +23,7 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React
     }
     promoteCollection?: () => void; // moves contents of collection to parent
     hasChildDocs?: () => Doc[];
+    docEditorView?: () => void;
     updateIcon?: (usePanelDimensions?: boolean) => Promise<void>; // updates the icon representation of the document
     getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
     restoreView?: (viewSpec: Doc) => boolean; // DEPRECATED: do not use, it will go away.  see PresBox.restoreTargetDocView
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ff6baf199..dd28832f9 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -316,6 +316,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return cropping;
     };
 
+    docEditorView = action(() => {
+        const field = Cast(this.dataDoc[this.fieldKey], ImageField);
+        if (field) {
+            ImageEditorData.Open = true;
+            ImageEditorData.Source = this.choosePath(field.url);
+            ImageEditorData.AddDoc = this._props.addDocument;
+            ImageEditorData.RootDoc = this.Document;
+        }
+    });
+
     specificContextMenu = (): void => {
         const field = Cast(this.dataDoc[this.fieldKey], ImageField);
         if (field) {
@@ -353,16 +363,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 icon: 'expand-arrows-alt',
             });
             funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
-            funcs.push({
-                description: 'Open Image Editor',
-                event: action(() => {
-                    ImageEditorData.Open = true;
-                    ImageEditorData.Source = this.choosePath(field.url);
-                    ImageEditorData.AddDoc = this._props.addDocument;
-                    ImageEditorData.RootDoc = this.Document;
-                }),
-                icon: 'pencil-alt',
-            });
+            funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' });
             this.layoutDoc.ai &&
                 funcs.push({
                     description: 'Regenerate AI Image',
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index c1f6f5859..009eb82cd 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -6,7 +6,7 @@ import interactionPlugin from '@fullcalendar/interaction';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { dateRangeStrToDates, simulateMouseClick } from '../../../../ClientUtils';
+import { dateRangeStrToDates } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
 import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView';
diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx
index 6b1d05031..3c0ab3da5 100644
--- a/src/client/views/nodes/imageEditor/ImageEditor.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx
@@ -90,18 +90,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
      * @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;
-        }
+        setCurrToolType(type);
+        setCursorData(prev => ({ ...prev, width: currTool().sliderDefault as number }));
     };
     // Undo and Redo
     const handleUndo = () => {
@@ -171,9 +161,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
 
     // handles brushing on pointer movement
     useEffect(() => {
-        if (!isBrushing) return undefined;
         const canvas = canvasRef.current;
-        if (!canvas) return undefined;
+        if (!isBrushing || !canvas) return undefined;
         const ctx = ImageUtility.getCanvasContext(canvasRef);
         if (!ctx) return undefined;
 
@@ -188,33 +177,29 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         };
 
         drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove);
-        return () => {
-            drawingAreaRef.current?.removeEventListener('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();
+        if (imageEditorSource && imageEditorSource) {
+            ImageUtility.urlToBase64(imageEditorSource).then(res => {
+                if (res) {
+                    const img = new Image();
+                    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 });
+                    };
+                }
+            });
+        }
 
         // cleanup
         return () => {
@@ -300,7 +285,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
             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);
+            const res = await ImageUtility.getEdit(imgBlob, maskBlob, input || 'Fill in the image in the same style', 2);
 
             // create first image
             if (!newCollectionRef.current) {
@@ -569,11 +554,15 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         setIsFirstDoc(true);
     };
 
+    function currTool() {
+        return imageEditTools.find(tool => tool.type === currToolType) ?? genFillTool;
+    }
+
     // 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 genFillTool: ImageEditTool = { type: ImageToolType.GenerativeFill, btnText: 'GET EDITS', icon: 'fill', applyFunc: getEdit, sliderMin: 25, sliderMax: 500, sliderDefault: 150 };
+    const cutTool: ImageEditTool = { type: ImageToolType.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);
+    const [currToolType, setCurrToolType] = useState<ImageToolType>(ImageToolType.GenerativeFill);
 
     // the top controls for making a new collection, resetting, and applying edits,
     function renderControls() {
@@ -595,7 +584,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                         labelPlacement="end"
                         sx={{ whiteSpace: 'nowrap' }}
                     />
-                    <ApplyFuncButtons onClick={() => currTool.applyFunc(cutType, cursorData.width, edits, isFirstDoc)} loading={loading} onReset={handleReset} btnText={currTool.btnText} />
+                    <ApplyFuncButtons onClick={() => currTool().applyFunc(cutType, cursorData.width, edits, isFirstDoc)} loading={loading} onReset={handleReset} btnText={currTool().btnText} />
                     <IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
                 </div>
             </div>
@@ -607,8 +596,8 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
         return (
             <div className="sideControlsContainer" style={{ backgroundColor: bgColor }}>
                 <div className="sideControls">
-                    <div className="imageToolsContainer">{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool.type, changeTool))}</div>
-                    {currTool.type == ImageToolType.Cut && (
+                    <div className="imageToolsContainer">{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool().type, changeTool))}</div>
+                    {currTool().type == ImageToolType.Cut && (
                         <div className="cutToolsContainer">
                             <Button style={{ width: '100%' }} text="Keep in" type={Type.TERT} color={cutType == CutMode.IN ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.IN)} />
                             <Button style={{ width: '100%' }} text="Keep out" type={Type.TERT} color={cutType == CutMode.OUT ? SettingsManager.userColor : bgColor} onClick={() => setCutType(CutMode.OUT)} />
@@ -617,7 +606,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                         </div>
                     )}
                     <div className="sliderContainer" onPointerDown={e => e.stopPropagation()}>
-                        {currTool.type === ImageToolType.GenerativeFill && (
+                        {currTool().type === ImageToolType.GenerativeFill && (
                             <Slider
                                 sx={{
                                     '& input[type="range"]': {
@@ -633,7 +622,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                                 onChange={(e, val) => setCursorData(prev => ({ ...prev, width: val as number }))}
                             />
                         )}
-                        {currTool.type === ImageToolType.Cut && (
+                        {currTool().type === ImageToolType.Cut && (
                             <Slider
                                 sx={{
                                     '& input[type="range"]': {
@@ -780,7 +769,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc
                 {renderSideIcons()}
                 {renderEditThumbnails()}
             </div>
-            {currTool.type === ImageToolType.GenerativeFill && renderPromptBox()}
+            {currTool().type === ImageToolType.GenerativeFill && renderPromptBox()}
         </div>
     );
 };
diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
index 985dc914f..3eaa251f2 100644
--- a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
@@ -53,10 +53,10 @@ export function ApplyFuncButtons({ loading, onClick: getEdit, onReset, btnText }
 
 export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTool: (type: ImageToolType) => void) {
     return (
-        <div key={tool.name} className="imageEditorButtonContainer">
+        <div key={tool.type} className="imageEditorButtonContainer">
             <Button
                 style={{ width: '100%' }}
-                text={tool.name}
+                text={tool.type}
                 type={Type.TERT}
                 color={isActive ? SettingsManager.userVariantColor : bgColor}
                 icon={<FontAwesomeIcon icon={tool.icon} />}
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
index ece0f4d7f..1c6a38a24 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts
@@ -87,7 +87,6 @@ export class ImageUtility {
                 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}`),
diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
index a14b55439..02dbc0312 100644
--- a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
+++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts
@@ -13,8 +13,8 @@ export interface Point {
 }
 
 export enum ImageToolType {
-    GenerativeFill = 'genFill',
-    Cut = 'cut',
+    GenerativeFill = 'Generative Fill',
+    Cut = 'Cut',
 }
 
 export enum CutMode {
@@ -26,7 +26,6 @@ export enum CutMode {
 
 export interface ImageEditTool {
     type: ImageToolType;
-    name: string;
     btnText: string;
     icon: IconProp;
     // this is the function that the image tool applies, so it can be defined depending on the tool
-- 
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/nodes/imageEditor/ImageEditor.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

