From ef5948e183b15009615818e22e9ea9c748abce2c Mon Sep 17 00:00:00 2001 From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com> Date: Thu, 10 Oct 2024 01:02:51 -0400 Subject: docCreatorMenu refactor work --- src/client/views/MainView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index fc159d96b..6ee42d256 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -58,7 +58,7 @@ import { ImageLabelHandler } from './collections/collectionFreeForm/ImageLabelHa import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { LinkMenu } from './linking/LinkMenu'; -import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu'; +import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; -- cgit v1.2.3-70-g09d2 From 3b17868560090756caf8b9b0f043ea163f2320e8 Mon Sep 17 00:00:00 2001 From: eleanor-park Date: Sun, 20 Oct 2024 12:44:15 -0400 Subject: changes --- src/client/views/MainView.tsx | 5 +- src/client/views/StyleProviderQuiz.tsx | 2 +- .../views/nodes/generativeFill/GenerativeFill.scss | 97 --- .../views/nodes/generativeFill/GenerativeFill.tsx | 687 ------------------- .../generativeFill/GenerativeFillButtons.scss | 4 - .../nodes/generativeFill/GenerativeFillButtons.tsx | 72 -- .../generativeFillUtils/BrushHandler.ts | 35 - .../GenerativeFillMathHelpers.ts | 6 - .../generativeFillUtils/ImageHandler.ts | 312 --------- .../generativeFillUtils/PointerHandler.ts | 11 - .../generativeFillUtils/generativeFillConstants.ts | 9 - .../generativeFillInterfaces.ts | 20 - .../nodes/imageEditor/GenerativeFillButtons.scss | 4 + .../nodes/imageEditor/GenerativeFillButtons.tsx | 72 ++ .../views/nodes/imageEditor/ImageEditor.scss | 134 ++++ src/client/views/nodes/imageEditor/ImageEditor.tsx | 758 +++++++++++++++++++++ .../views/nodes/imageEditor/ImageEditorButtons.tsx | 69 ++ .../imageEditor/imageEditingUtils/ImageHandler.ts | 312 +++++++++ .../imageEditor/imageEditorUtils/BrushHandler.ts | 35 + .../imageEditorUtils/GenerativeFillMathHelpers.ts | 6 + .../imageEditor/imageEditorUtils/ImageHandler.ts | 312 +++++++++ .../imageEditor/imageEditorUtils/PointerHandler.ts | 11 + .../imageEditorUtils/imageEditorConstants.ts | 9 + .../imageEditorUtils/imageEditorInterfaces.ts | 38 ++ .../imageEditor/imageToolUtils/BrushHandler.ts | 35 + .../imageEditor/imageToolUtils/ImageHandler.ts | 312 +++++++++ src/client/views/smartdraw/AnnotationPalette.tsx | 2 +- 27 files changed, 2112 insertions(+), 1257 deletions(-) delete mode 100644 src/client/views/nodes/generativeFill/GenerativeFill.scss delete mode 100644 src/client/views/nodes/generativeFill/GenerativeFill.tsx delete mode 100644 src/client/views/nodes/generativeFill/GenerativeFillButtons.scss delete mode 100644 src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts delete mode 100644 src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts create mode 100644 src/client/views/nodes/imageEditor/GenerativeFillButtons.scss create mode 100644 src/client/views/nodes/imageEditor/GenerativeFillButtons.tsx create mode 100644 src/client/views/nodes/imageEditor/ImageEditor.scss create mode 100644 src/client/views/nodes/imageEditor/ImageEditor.tsx create mode 100644 src/client/views/nodes/imageEditor/ImageEditorButtons.tsx create mode 100644 src/client/views/nodes/imageEditor/imageEditingUtils/ImageHandler.ts create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/GenerativeFillMathHelpers.ts create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/PointerHandler.ts create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts create mode 100644 src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts create mode 100644 src/client/views/nodes/imageEditor/imageToolUtils/BrushHandler.ts create mode 100644 src/client/views/nodes/imageEditor/imageToolUtils/ImageHandler.ts (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7779d339f..7bc211036 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -73,7 +73,7 @@ import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; -import GenerativeFill from './nodes/generativeFill/GenerativeFill'; +import ImageEditorBox from './nodes/imageEditor/ImageEditor'; import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; @@ -446,6 +446,7 @@ export class MainView extends ObservableReactComponent { fa.faAlignRight, fa.faHeading, fa.faRulerCombined, + fa.faFill, fa.faFillDrip, fa.faLink, fa.faUnlink, @@ -1146,7 +1147,7 @@ export class MainView extends ObservableReactComponent { - + ); } diff --git a/src/client/views/StyleProviderQuiz.tsx b/src/client/views/StyleProviderQuiz.tsx index 1f2ad1485..8a1515d21 100644 --- a/src/client/views/StyleProviderQuiz.tsx +++ b/src/client/views/StyleProviderQuiz.tsx @@ -17,7 +17,7 @@ import { AnchorMenu } from './pdf/AnchorMenu'; import { DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { ImageBox } from './nodes/ImageBox'; -import { ImageUtility } from './nodes/generativeFill/generativeFillUtils/ImageHandler'; +import { ImageUtility } from './nodes/imageEditor/imageEditorUtils/ImageHandler'; import './StyleProviderQuiz.scss'; export namespace styleProviderQuiz { diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss deleted file mode 100644 index c2669a950..000000000 --- a/src/client/views/nodes/generativeFill/GenerativeFill.scss +++ /dev/null @@ -1,97 +0,0 @@ -$navHeight: 5rem; -$canvasSize: 1024px; -$scale: 0.5; - -.generativeFillContainer { - position: absolute; - top: 0; - left: 0; - z-index: 9999; - height: 100vh; - width: 100vw; - display: flex; - flex-direction: column; - overflow: hidden; - - .generativeFillControls { - flex-shrink: 0; - height: $navHeight; - color: #000000; - background-color: #ffffff; - z-index: 999; - width: 100%; - display: flex; - gap: 3rem; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid #c7cdd0; - padding: 0 2rem; - - h1 { - font-size: 1.5rem; - } - } - - .drawingArea { - cursor: none; - touch-action: none; - position: relative; - flex-grow: 1; - display: flex; - justify-content: center; - align-items: center; - width: 100%; - background-color: #f0f4f6; - - canvas { - display: block; - position: absolute; - transform-origin: 50% 50%; - } - - .pointer { - pointer-events: none; - position: absolute; - border-radius: 50%; - width: 50px; - height: 50px; - border: 1px solid #ffffff; - transform: translate(-50%, -50%); - display: flex; - justify-content: center; - align-items: center; - - .innerPointer { - width: 100%; - height: 100%; - border: 1px solid #000000; - border-radius: 50%; - } - } - - .iconContainer { - position: absolute; - top: 2rem; - left: 2rem; - display: flex; - flex-direction: column; - gap: 2rem; - } - - .editsBox { - position: absolute; - top: 2rem; - right: 2rem; - display: flex; - flex-direction: column; - gap: 1rem; - - img { - transition: all 0.2s ease-in-out; - &:hover { - opacity: 0.8; - } - } - } - } -} diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx deleted file mode 100644 index 261eb4bb4..000000000 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ /dev/null @@ -1,687 +0,0 @@ -/* eslint-disable jsx-a11y/label-has-associated-control */ -/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ -/* eslint-disable jsx-a11y/img-redundant-alt */ -/* eslint-disable jsx-a11y/click-events-have-key-events */ -/* eslint-disable react/function-component-definition */ -import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material'; -import { IconButton } from 'browndash-components'; -import * as React from 'react'; -import { useEffect, useRef, useState } from 'react'; -import { CgClose } from 'react-icons/cg'; -import { IoMdRedo, IoMdUndo } from 'react-icons/io'; -import { ClientUtils } from '../../../../ClientUtils'; -import { Doc, DocListCast } from '../../../../fields/Doc'; -import { List } from '../../../../fields/List'; -import { NumCast } from '../../../../fields/Types'; -import { Networking } from '../../../Network'; -import { DocUtils } from '../../../documents/DocUtils'; -import { Docs } from '../../../documents/Documents'; -import { CollectionDockingView } from '../../collections/CollectionDockingView'; -import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; -import { ImageEditorData } from '../ImageBox'; -import { OpenWhereMod } from '../OpenWhere'; -import './GenerativeFill.scss'; -import { EditButtons, CutButtons } from './GenerativeFillButtons'; -import { BrushHandler, BrushType } from './generativeFillUtils/BrushHandler'; -import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; -import { PointerHandler } from './generativeFillUtils/PointerHandler'; -import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; -import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces'; -import { DocumentView } from '../DocumentView'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ImageField } from '../../../../fields/URLField'; -import { resolve } from 'url'; - -interface GenerativeFillProps { - imageEditorOpen: boolean; - imageEditorSource: string; - imageRootDoc: Doc | undefined; - addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined; -} - -// Added field on image doc: gen_fill_children: List of children Docs - -const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => { - const canvasRef = useRef(null); - const canvasBackgroundRef = useRef(null); - const drawingAreaRef = useRef(null); - const [cursorData, setCursorData] = useState({ - x: 0, - y: 0, - width: 150, - }); - const [isBrushing, setIsBrushing] = useState(false); - const [canvasScale, setCanvasScale] = useState(0.5); - // format: array of [image source, corresponding image Doc] - const [edits, setEdits] = useState<{ url: string; saveRes: Doc | undefined }[]>([]); - const [edited, setEdited] = useState(false); - // const [brushStyle] = useState(BrushStyle.ADD); - const [input, setInput] = useState(''); - const [loading, setLoading] = useState(false); - const [canvasDims, setCanvasDims] = useState({ - width: canvasSize, - height: canvasSize, - }); - // whether to create a new collection or not - const [isNewCollection, setIsNewCollection] = useState(true); - // the current image in the main canvas - const currImg = useRef(null); - // the unedited version of each generation (parent) - const originalImg = useRef(null); - const originalDoc = useRef(null); - // stores history of data urls - const undoStack = useRef([]); - // stores redo stack - const redoStack = useRef([]); - - // references to keep track of tree structure - const newCollectionRef = useRef(null); - const parentDoc = useRef(null); - const childrenDocs = useRef([]); - - // constants for image cutting - const cutPts = useRef([]); - - // Undo and Redo - const handleUndo = () => { - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx || !currImg.current || !canvasRef.current) return; - - const target = undoStack.current[undoStack.current.length - 1]; - if (!target) { - ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height); - } else { - redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()]; - const img = new Image(); - img.src = target; - ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); - undoStack.current = undoStack.current.slice(0, -1); - } - }; - - const handleRedo = () => { - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx || !currImg.current || !canvasRef.current) return; - - const target = redoStack.current[redoStack.current.length - 1]; - if (target) { - undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()]; - const img = new Image(); - img.src = target; - ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); - redoStack.current = redoStack.current.slice(0, -1); - } - }; - - // resets any erase strokes - const handleReset = () => { - if (!canvasRef.current || !currImg.current) return; - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx) return; - ctx.clearRect(0, 0, canvasSize, canvasSize); - undoStack.current = []; - redoStack.current = []; - ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height); - }; - - // initiate brushing - const handlePointerDown = (e: React.PointerEvent) => { - const canvas = canvasRef.current; - if (!canvas) return; - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx) return; - - undoStack.current = [...undoStack.current, canvasRef.current.toDataURL()]; - redoStack.current = []; - - setIsBrushing(true); - const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale); - BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor /* , brushStyle === BrushStyle.SUBTRACT */); - }; - - // stop brushing, push to undo stack - const handlePointerUp = () => { - const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef); - if (!ctx) return; - if (!isBrushing) return; - setIsBrushing(false); - }; - - // handles brushing on pointer movement - useEffect(() => { - if (!isBrushing) return undefined; - const canvas = canvasRef.current; - if (!canvas) return undefined; - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx) return undefined; - - const handlePointerMove = (e: PointerEvent) => { - const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale); - const lastPoint: Point = { - x: currPoint.x - e.movementX / canvasScale, - y: currPoint.y - e.movementY / canvasScale, - }; - const pts = BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, BrushType.CUT); - cutPts.current.push(...pts); - }; - - drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove); - return () => { - drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove); - }; - }, [isBrushing]); - - // first load - useEffect(() => { - const loadInitial = async () => { - if (!imageEditorSource || imageEditorSource === '') return; - const img = new Image(); - const res = await ImageUtility.urlToBase64(imageEditorSource); - if (!res) return; - img.src = `data:image/png;base64,${res}`; - - img.onload = () => { - currImg.current = img; - originalImg.current = img; - const imgWidth = img.naturalWidth; - const imgHeight = img.naturalHeight; - const scale = Math.min(canvasSize / imgWidth, canvasSize / imgHeight); - const width = imgWidth * scale; - const height = imgHeight * scale; - setCanvasDims({ width, height }); - }; - }; - - loadInitial(); - - // cleanup - return () => { - setInput(''); - setEdited(false); - newCollectionRef.current = null; - parentDoc.current = null; - childrenDocs.current = []; - currImg.current = null; - originalImg.current = null; - originalDoc.current = null; - undoStack.current = []; - redoStack.current = []; - ImageUtility.clearCanvas(canvasRef); - }; - }, [canvasRef, imageEditorSource]); - - // once the appropriate dimensions are set, draw the image to the canvas - useEffect(() => { - if (!currImg.current) return; - ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height); - }, [canvasDims]); - - // handles brush sizing - useEffect(() => { - const handleKeyPress = (e: KeyboardEvent) => { - if (e.key === 'ArrowUp') { - e.preventDefault(); - e.stopPropagation(); - setCursorData(data => ({ ...data, width: data.width + 5 })); - } else if (e.key === 'ArrowDown') { - e.preventDefault(); - e.stopPropagation(); - setCursorData(data => (data.width >= 20 ? { ...data, width: data.width - 5 } : data)); - } - }; - window.addEventListener('keydown', handleKeyPress); - return () => window.removeEventListener('keydown', handleKeyPress); - }, []); - - // handle pinch zoom - useEffect(() => { - const handlePinch = (e: WheelEvent) => { - e.preventDefault(); - e.stopPropagation(); - const delta = e.deltaY; - const scaleFactor = delta > 0 ? 0.98 : 1.02; - setCanvasScale(prevScale => prevScale * scaleFactor); - }; - - drawingAreaRef.current?.addEventListener('wheel', handlePinch, { - passive: false, - }); - return () => drawingAreaRef.current?.removeEventListener('wheel', handlePinch); - }, [drawingAreaRef]); - - // updates the current position of the cursor - const updateCursorData = (e: React.PointerEvent) => { - const drawingArea = drawingAreaRef.current; - if (!drawingArea) return; - const { x, y } = PointerHandler.getPointRelativeToElement(drawingArea, e, 1); - setCursorData(data => ({ - ...data, - x, - y, - })); - }; - - // Get AI Edit - const getEdit = async () => { - const img = currImg.current; - if (!img) return; - const canvas = canvasRef.current; - if (!canvas) return; - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx) return; - setLoading(true); - setEdited(true); - try { - const canvasOriginalImg = ImageUtility.getCanvasImg(img); - if (!canvasOriginalImg) return; - const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg); - if (!canvasMask) return; - const maskBlob = await ImageUtility.canvasToBlob(canvasMask); - const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg); - const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2); - - // create first image - if (!newCollectionRef.current) { - if (!isNewCollection && imageRootDoc) { - // if the parent hasn't been set yet - if (!parentDoc.current) parentDoc.current = imageRootDoc; - } else { - if (!(originalImg.current && imageRootDoc)) return; - // create new collection and add it to the view - newCollectionRef.current = Docs.Create.FreeformDocument([], { - x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, - y: NumCast(imageRootDoc.y), - _width: newCollectionSize, - _height: newCollectionSize, - title: 'Image edit collection', - }); - DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History' }); - - // opening new tab - CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); - - // add the doc to the main freeform - // eslint-disable-next-line no-use-before-define - await createNewImgDoc(originalImg.current, true); - } - } else { - childrenDocs.current = []; - } - - originalImg.current = currImg.current; - originalDoc.current = parentDoc.current; - const { urls } = res as APISuccess; - if (res.status !== 'error') { - const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height))); - const imgRes = await Promise.all( - imgUrls.map(async url => { - // eslint-disable-next-line no-use-before-define - const saveRes = await onSave(url); - return { url, saveRes }; - }) - ); - setEdits(imgRes); - const image = new Image(); - // eslint-disable-next-line prefer-destructuring - image.src = imgUrls[0]; - ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height); - currImg.current = image; - parentDoc.current = imgRes[0].saveRes ?? null; - } - } catch (err) { - console.log(err); - } - setLoading(false); - }; - - const cutImage = async () => { - const img = currImg.current; - const canvas = canvasRef.current; - if (!canvas || !img) return; - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = ImageUtility.getCanvasContext(canvasRef); - if (!ctx) return; - ctx.globalCompositeOperation = 'source-over'; - setLoading(true); - setEdited(true); - // get the original image - const canvasOriginalImg = ImageUtility.getCanvasImg(img); - if (!canvasOriginalImg) return; - // draw the image onto the canvas - ctx.drawImage(img, 0, 0); - // get the mask which i assume is the thing the user draws on - // const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg); - // if (!canvasMask) return; - // canvasMask.width = canvas.width; - // canvasMask.height = canvas.height; - // now put the user's path around the mask - if (cutPts.current.length) { - ctx.beginPath(); - ctx.moveTo(cutPts.current[0].x, cutPts.current[0].y); // later check edge case where cutPts is empty - for (let i = 0; i < cutPts.current.length; i++) { - ctx.lineTo(cutPts.current[i].x, cutPts.current[i].y); - } - ctx.closePath(); - ctx.stroke(); - ctx.fill(); - // ctx.clip(); - } - const url = canvas.toDataURL(); // this does the same thing as convert img to canvasurl - if (!newCollectionRef.current) { - if (!isNewCollection && imageRootDoc) { - // if the parent hasn't been set yet - if (!parentDoc.current) parentDoc.current = imageRootDoc; - } else { - if (!(originalImg.current && imageRootDoc)) return; - // create new collection and add it to the view - newCollectionRef.current = Docs.Create.FreeformDocument([], { - x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX, - y: NumCast(imageRootDoc.y), - _width: newCollectionSize, - _height: newCollectionSize, - title: 'Image edit collection', - }); - DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History' }); - // opening new tab - CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); - } - } - const image = new Image(); - image.src = url; - await createNewImgDoc(image, true); - // add the doc to the main freeform - // eslint-disable-next-line no-use-before-define - setLoading(false); - cutPts.current.length = 0; - }; - - // adjusts all the img positions to be aligned - const adjustImgPositions = () => { - if (!parentDoc.current) return; - const startY = NumCast(parentDoc.current.y); - const children = DocListCast(parentDoc.current.gen_fill_children); - const len = children.length; - const initialYPositions: number[] = []; - for (let i = 0; i < len; i++) { - initialYPositions.push(startY + i * offsetDistanceY); - } - children.forEach((doc, i) => { - if (len % 2 === 1) { - doc.y = initialYPositions[i] - Math.floor(len / 2) * offsetDistanceY; - } else { - doc.y = initialYPositions[i] - (len / 2 - 1 / 2) * offsetDistanceY; - } - }); - }; - - // creates a new image document and returns its reference - const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise => { - 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(parentList); - } else { - parentDoc.current.gen_fill_children = new List([newImg]); - } - - DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}` }); - adjustImgPositions(); - - if (isNewCollection && newCollectionRef.current) { - Doc.AddDocToList(newCollectionRef.current, undefined, newImg); - } else { - addDoc?.(newImg); - } - return newImg; - }; - - // Saves an image to the collection - const onSave = async (src: string) => { - const img = new Image(); - img.src = src; - if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined; - try { - const res = await createNewImgDoc(img, false); - return res; - } catch (err) { - console.log(err); - } - return undefined; - }; - - // Closes the editor view - const handleViewClose = () => { - ImageEditorData.Open = false; - ImageEditorData.Source = ''; - if (newCollectionRef.current) { - DocumentView.addViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce()); - } - setEdits([]); - }; - - return ( -
-
-

Image Editor

- {/* } /> */} -
- { - setIsNewCollection(prev => !prev); - }} - /> - } - label="Create New Collection" - labelPlacement="end" - sx={{ whiteSpace: 'nowrap' }} - /> - - - } onClick={handleViewClose} /> -
-
- {/* Main canvas for editing */} -
- - -
-
-
- {/* Icons */} -
- {/* Undo and Redo */} - { - e.stopPropagation(); - handleUndo(); - }} - onPointerUp={e => { - e.stopPropagation(); - }} - color={activeColor} - tooltip="Undo" - icon={} - /> - { - e.stopPropagation(); - handleRedo(); - }} - onPointerUp={e => { - e.stopPropagation(); - }} - color={activeColor} - tooltip="Redo" - icon={} - /> -
e.stopPropagation()} style={{ height: 225, width: '100%', display: 'flex', justifyContent: 'center', cursor: 'pointer' }}> - { - setCursorData(prev => ({ ...prev, width: val as number })); - }} - /> -
-
e.stopPropagation()} style={{ height: 225, width: '100%', display: 'flex', justifyContent: 'center', cursor: 'pointer' }}> - { - setCursorData(prev => ({ ...prev, width: val as number })); - }} - /> -
-
- {/* Edits thumbnails */} -
- {edits.map((edit, i) => ( - image edits { - const img = new Image(); - img.src = edit.url; - ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); - currImg.current = img; - parentDoc.current = edit.saveRes ?? null; - }} - /> - ))} - {/* Original img thumbnail */} - {edits.length > 0 && ( -
- - image stuff { - if (!originalImg.current) return; - const img = new Image(); - img.src = originalImg.current.src; - ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height); - currImg.current = img; - parentDoc.current = originalDoc.current; - }} - /> -
- )} -
-
-
- 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)', - }} - /> -
-
- ); -}; - -export default GenerativeFill; diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss b/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss deleted file mode 100644 index 0180ef904..000000000 --- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.scss +++ /dev/null @@ -1,4 +0,0 @@ -.generativeFillBtnContainer { - display: flex; - gap: 1rem; -} diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx deleted file mode 100644 index fe22b273d..000000000 --- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import './GenerativeFillButtons.scss'; -import * as React from 'react'; -import ReactLoading from 'react-loading'; -import { Button, IconButton, Type } from 'browndash-components'; -import { AiOutlineInfo } from 'react-icons/ai'; -import { activeColor } from './generativeFillUtils/generativeFillConstants'; - -interface ButtonContainerProps { - onClick: () => Promise; - loading: boolean; - onReset: () => void; -} - -export function EditButtons({ loading, onClick: getEdit, onReset }: ButtonContainerProps) { - return ( -
-
- ); -} - -export function CutButtons({ loading, onClick: cutImage, onReset }: ButtonContainerProps) { - return ( -
-
- ); -} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts deleted file mode 100644 index 8a66d7347..000000000 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { GenerativeFillMathHelpers } from './GenerativeFillMathHelpers'; -import { eraserColor } from './generativeFillConstants'; -import { Point } from './generativeFillInterfaces'; -import { points } from '@turf/turf'; - -export enum BrushType { - GEN_FILL, - CUT, -} - -export class BrushHandler { - static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => { - ctx.globalCompositeOperation = 'destination-out'; - ctx.fillStyle = fillColor; - ctx.shadowColor = eraserColor; - ctx.shadowBlur = 5; - ctx.beginPath(); - ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); - ctx.fill(); - ctx.closePath(); - }; - - static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, brushType: BrushType) => { - const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint); - const pts: Point[] = []; - for (let i = 0; i < dist; i += 5) { - const s = i / dist; - const x = startPoint.x * (1 - s) + endPoint.x * s; - const y = startPoint.y * (1 - s) + endPoint.y * s; - pts.push({ x: startPoint.x, y: startPoint.y }); - BrushHandler.brushCircleOverlay(x, y, brushRadius, ctx, fillColor); - } - return pts; - }; -} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts deleted file mode 100644 index 6da8c3da0..000000000 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Point } from './generativeFillInterfaces'; - -export class GenerativeFillMathHelpers { - static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2); - static angleBetween = (p1: Point, p2: Point) => Math.atan2(p2.x - p1.x, p2.y - p1.y); -} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts deleted file mode 100644 index 24dba1778..000000000 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { RefObject } from 'react'; -import { bgColor, canvasSize } from './generativeFillConstants'; - -export interface APISuccess { - status: 'success'; - urls: string[]; -} - -export interface APIError { - status: 'error'; - message: string; -} - -export class ImageUtility { - /** - * - * @param canvas Canvas to convert - * @returns Blob of canvas - */ - static canvasToBlob = (canvas: HTMLCanvasElement): Promise => - new Promise(resolve => { - canvas.toBlob(blob => { - if (blob) { - resolve(blob); - } - }, 'image/png'); - }); - - // given a square api image, get the cropped img - static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => { - // Create a new canvas element - const canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext('2d'); - if (ctx) { - // Clear the canvas - ctx.clearRect(0, 0, canvas.width, canvas.height); - if (width < height) { - // horizontal padding, x offset - const xOffset = (canvasSize - width) / 2; - ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); - } else { - // vertical padding, y offset - const yOffset = (canvasSize - height) / 2; - ctx.drawImage(img, 0, yOffset, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); - } - return canvas; - } - return undefined; - }; - - // converts an image to a canvas data url - static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise => - new Promise((resolve, reject) => { - const img = new Image(); - img.onload = () => { - const canvas = this.getCroppedImg(img, width, height); - if (canvas) { - const dataUrl = canvas.toDataURL(); - resolve(dataUrl); - } - }; - img.onerror = error => { - reject(error); - }; - img.src = imageSrc; - }); - - // calls the openai api to get image edits - static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise => { - const apiUrl = 'https://api.openai.com/v1/images/edits'; - const fd = new FormData(); - fd.append('image', imgBlob, 'image.png'); - fd.append('mask', maskBlob, 'mask.png'); - fd.append('prompt', prompt); - fd.append('size', '1024x1024'); - fd.append('n', n ? JSON.stringify(n) : '1'); - fd.append('response_format', 'b64_json'); - - try { - const res = await fetch(apiUrl, { - method: 'POST', - headers: { - Authorization: `Bearer ${process.env.OPENAI_KEY}`, - }, - body: fd, - }); - const data = await res.json(); - console.log(data.data); - return { - status: 'success', - urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`), - }; - } catch (err) { - console.log(err); - return { status: 'error', message: 'API error.' }; - } - }; - - // mock api call - static mockGetEdit = async (mockSrc: string): Promise => ({ - status: 'success', - urls: [mockSrc, mockSrc, mockSrc], - }); - - // Gets the canvas rendering context of a canvas - static getCanvasContext = (canvasRef: RefObject): CanvasRenderingContext2D | null => { - if (!canvasRef.current) return null; - const ctx = canvasRef.current.getContext('2d'); - if (!ctx) return null; - return ctx; - }; - - // Helper for downloading the canvas (for debugging) - static downloadCanvas = (canvas: HTMLCanvasElement) => { - const url = canvas.toDataURL(); - const downloadLink = document.createElement('a'); - downloadLink.href = url; - downloadLink.download = 'canvas'; - - downloadLink.click(); - downloadLink.remove(); - }; - - // Download the canvas (for debugging) - static downloadImageCanvas = (imgUrl: string) => { - const img = new Image(); - img.src = imgUrl; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = canvasSize; - canvas.height = canvasSize; - const ctx = canvas.getContext('2d'); - ctx?.drawImage(img, 0, 0, canvasSize, canvasSize); - - this.downloadCanvas(canvas); - }; - }; - - // Clears the canvas - static clearCanvas = (canvasRef: React.RefObject) => { - const ctx = this.getCanvasContext(canvasRef); - if (!ctx || !canvasRef.current) return; - ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); - }; - - // Draws the image to the current canvas - static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject, width: number, height: number) => { - const drawImg = (htmlImg: HTMLImageElement) => { - const ctx = this.getCanvasContext(canvasRef); - if (!ctx) return; - ctx.globalCompositeOperation = 'source-over'; - ctx.clearRect(0, 0, width, height); - ctx.drawImage(htmlImg, 0, 0, width, height); - }; - - if (img.complete) { - drawImg(img); - } else { - img.onload = () => { - drawImg(img); - }; - } - }; - - // Gets the image mask for the openai endpoint - static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => { - const canvas = document.createElement('canvas'); - canvas.width = canvasSize; - canvas.height = canvasSize; - const ctx = canvas.getContext('2d'); - if (!ctx) return undefined; - ctx?.clearRect(0, 0, canvasSize, canvasSize); - ctx.drawImage(paddedCanvas, 0, 0); - - // extract and set padding data - if (srcCanvas.height > srcCanvas.width) { - // horizontal padding, x offset - const xOffset = (canvasSize - srcCanvas.width) / 2; - ctx?.clearRect(xOffset, 0, srcCanvas.width, srcCanvas.height); - ctx.drawImage(srcCanvas, xOffset, 0, srcCanvas.width, srcCanvas.height); - } else { - // vertical padding, y offset - const yOffset = (canvasSize - srcCanvas.height) / 2; - ctx?.clearRect(0, yOffset, srcCanvas.width, srcCanvas.height); - ctx.drawImage(srcCanvas, 0, yOffset, srcCanvas.width, srcCanvas.height); - } - return canvas; - }; - - // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) - static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => { - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const { data } = imageData; - for (let i = 0; i < canvas.height; i++) { - for (let j = 0; j < xOffset; j++) { - const targetIdx = 4 * (i * canvas.width + j); - const sourceI = i; - const sourceJ = xOffset + (xOffset - j); - const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); - data[targetIdx] = data[sourceIdx]; - data[targetIdx + 1] = data[sourceIdx + 1]; - data[targetIdx + 2] = data[sourceIdx + 2]; - } - } - for (let i = 0; i < canvas.height; i++) { - for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) { - const targetIdx = 4 * (i * canvas.width + j); - const sourceI = i; - const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j)); - const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); - data[targetIdx] = data[sourceIdx]; - data[targetIdx + 1] = data[sourceIdx + 1]; - data[targetIdx + 2] = data[sourceIdx + 2]; - } - } - ctx.putImageData(imageData, 0, 0); - }; - - // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) - static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => { - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const { data } = imageData; - for (let j = 0; j < canvas.width; j++) { - for (let i = 0; i < yOffset; i++) { - const targetIdx = 4 * (i * canvas.width + j); - const sourceJ = j; - const sourceI = yOffset + (yOffset - i); - const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); - data[targetIdx] = data[sourceIdx]; - data[targetIdx + 1] = data[sourceIdx + 1]; - data[targetIdx + 2] = data[sourceIdx + 2]; - } - } - for (let j = 0; j < canvas.width; j++) { - for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) { - const targetIdx = 4 * (i * canvas.width + j); - const sourceJ = j; - const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i)); - const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); - data[targetIdx] = data[sourceIdx]; - data[targetIdx + 1] = data[sourceIdx + 1]; - data[targetIdx + 2] = data[sourceIdx + 2]; - } - } - ctx.putImageData(imageData, 0, 0); - }; - - // Gets the unaltered (besides filling in padding) version of the image for the api call - static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => { - const canvas = document.createElement('canvas'); - canvas.width = canvasSize; - canvas.height = canvasSize; - const ctx = canvas.getContext('2d'); - if (!ctx) return undefined; - // fix scaling - const scale = Math.min(canvasSize / img.width, canvasSize / img.height); - const width = Math.floor(img.width * scale); - const height = Math.floor(img.height * scale); - ctx?.clearRect(0, 0, canvasSize, canvasSize); - ctx.fillStyle = bgColor; - ctx.fillRect(0, 0, canvasSize, canvasSize); - - // extract and set padding data - if (img.naturalHeight > img.naturalWidth) { - // horizontal padding, x offset - const xOffset = Math.floor((canvasSize - width) / 2); - ctx.drawImage(img, xOffset, 0, width, height); - - // draw reflected image padding - this.drawHorizontalReflection(ctx, canvas, xOffset); - } else { - // vertical padding, y offset - const yOffset = Math.floor((canvasSize - height) / 2); - ctx.drawImage(img, 0, yOffset, width, height); - - // draw reflected image padding - this.drawVerticalReflection(ctx, canvas, yOffset); - } - return canvas; - }; - - /** - * Converts a url to base64 (tainted canvas workaround) - */ - static urlToBase64 = async (imageUrl: string): Promise => { - try { - const res = await fetch(imageUrl); - const blob = await res.blob(); - - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => { - const base64Data = reader.result?.toString().split(',')[1]; - if (base64Data) { - resolve(base64Data); - } else { - reject(new Error('Failed to convert.')); - } - }; - reader.onerror = () => { - reject(new Error('Error reading image data')); - }; - reader.readAsDataURL(blob); - }); - } catch (err) { - console.error(err); - } - return undefined; - }; -} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts deleted file mode 100644 index 260923a64..000000000 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/PointerHandler.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Point } from './generativeFillInterfaces'; - -export class PointerHandler { - static getPointRelativeToElement = (element: HTMLElement, e: React.PointerEvent | PointerEvent, scale: number): Point => { - const boundingBox = element.getBoundingClientRect(); - return { - x: (e.clientX - boundingBox.x) / scale, - y: (e.clientY - boundingBox.y) / scale, - }; - }; -} diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts deleted file mode 100644 index 4772304bc..000000000 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const canvasSize = 1024; -export const freeformRenderSize = 300; -export const offsetDistanceY = freeformRenderSize + 400; -export const offsetX = 200; -export const newCollectionSize = 500; - -export const activeColor = '#1976d2'; -export const eraserColor = '#e1e9ec'; -export const bgColor = '#f0f4f6'; diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts deleted file mode 100644 index 1e7801056..000000000 --- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface CursorData { - x: number; - y: number; - width: number; -} - -export interface Point { - x: number; - y: number; -} - -export enum BrushMode { - ADD, - SUBTRACT, -} - -export interface ImageDimensions { - width: number; - height: number; -} diff --git a/src/client/views/nodes/imageEditor/GenerativeFillButtons.scss b/src/client/views/nodes/imageEditor/GenerativeFillButtons.scss new file mode 100644 index 000000000..0180ef904 --- /dev/null +++ b/src/client/views/nodes/imageEditor/GenerativeFillButtons.scss @@ -0,0 +1,4 @@ +.generativeFillBtnContainer { + display: flex; + gap: 1rem; +} diff --git a/src/client/views/nodes/imageEditor/GenerativeFillButtons.tsx b/src/client/views/nodes/imageEditor/GenerativeFillButtons.tsx new file mode 100644 index 000000000..32ed6b307 --- /dev/null +++ b/src/client/views/nodes/imageEditor/GenerativeFillButtons.tsx @@ -0,0 +1,72 @@ +import './GenerativeFillButtons.scss'; +import * as React from 'react'; +import ReactLoading from 'react-loading'; +import { Button, IconButton, Type } from 'browndash-components'; +import { AiOutlineInfo } from 'react-icons/ai'; +import { activeColor } from './imageEditorUtils/imageEditorConstants'; + +interface ButtonContainerProps { + onClick: () => Promise; + loading: boolean; + onReset: () => void; +} + +export function EditButtons({ loading, onClick: getEdit, onReset }: ButtonContainerProps) { + return ( +
+
+ ); +} + +export function CutButtons({ loading, onClick: cutImage, onReset }: ButtonContainerProps) { + return ( +
+
+ ); +} diff --git a/src/client/views/nodes/imageEditor/ImageEditor.scss b/src/client/views/nodes/imageEditor/ImageEditor.scss new file mode 100644 index 000000000..21c28f6da --- /dev/null +++ b/src/client/views/nodes/imageEditor/ImageEditor.scss @@ -0,0 +1,134 @@ +$navHeight: 5rem; +$canvasSize: 1024px; +$scale: 0.5; + +.imageEditorContainer { + position: absolute; + top: 0; + left: 0; + z-index: 9999; + height: 100vh; + width: 100vw; + display: flex; + flex-direction: column; + overflow: hidden; + + .imageEditorTopBar { + flex-shrink: 0; + height: $navHeight; + color: #000000; + background-color: #ffffff; + z-index: 999; + width: 100%; + display: flex; + gap: 3rem; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #c7cdd0; + padding: 0 2rem; + + .imageEditorControls { + display: flex; + align-items: center; + gap: 1.5rem; + } + + h1 { + font-size: 1.5rem; + } + } + + .drawingArea { + cursor: none; + touch-action: none; + position: relative; + flex-grow: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + background-color: #f0f4f6; + + canvas { + display: block; + position: absolute; + transform-origin: 50% 50%; + } + + .pointer { + pointer-events: none; + position: absolute; + border-radius: 50%; + width: 50px; + height: 50px; + border: 1px solid #ffffff; + transform: translate(-50%, -50%); + display: flex; + justify-content: center; + align-items: center; + + .innerPointer { + width: 100%; + height: 100%; + border: 1px solid #000000; + border-radius: 50%; + } + } + + .iconContainer { + position: absolute; + top: 3rem; + left: 2rem; + display: flex; + flex-direction: column; + gap: 4rem; + + .imageToolsContainer { + display: flex; + flex-direction: column; + gap: 10px; + } + + .undoRedoContainer { + justify-content: center; + display: flex; + flex-direction: row; + } + + .sliderContainer { + height: 225px; + width: 100%; + display: flex; + justify-content: center; + cursor: pointer; + } + } + + .editsBox { + position: absolute; + top: 2rem; + right: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; + + .originalImageLabel { + position: absolute; + bottom: 10; + left: 10; + color: #ffffff; + font-size: 0.8rem; + letter-spacing: 1px; + text-transform: uppercase; + } + + img { + cursor: pointer; + transition: all 0.2s ease-in-out; + &:hover { + opacity: 0.8; + } + } + } + } +} diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx new file mode 100644 index 000000000..86f7d8d29 --- /dev/null +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -0,0 +1,758 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +/* eslint-disable jsx-a11y/img-redundant-alt */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable react/function-component-definition */ +import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material'; +import { IconButton } from 'browndash-components'; +import * as React from 'react'; +import { useEffect, useRef, useState } from 'react'; +import { CgClose } from 'react-icons/cg'; +import { IoMdRedo, IoMdUndo } from 'react-icons/io'; +import { ClientUtils } from '../../../../ClientUtils'; +import { Doc, DocListCast } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; +import { NumCast } from '../../../../fields/Types'; +import { Networking } from '../../../Network'; +import { DocUtils } from '../../../documents/DocUtils'; +import { Docs } from '../../../documents/Documents'; +import { CollectionDockingView } from '../../collections/CollectionDockingView'; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; +import { ImageEditorData } from '../ImageBox'; +import { OpenWhereMod } from '../OpenWhere'; +import './ImageEditor.scss'; +import { ApplyFuncButtons, ImageToolButton } from './ImageEditorButtons'; +import { BrushHandler, BrushType } from './imageEditorUtils/BrushHandler'; +import { APISuccess, ImageUtility } from './imageEditorUtils/ImageHandler'; +import { PointerHandler } from './imageEditorUtils/PointerHandler'; +import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './imageEditorUtils/imageEditorConstants'; +import { CursorData, ImageDimensions, ImageEditTool, ImageToolType, Point } from './imageEditorUtils/imageEditorInterfaces'; +import { DocumentView } from '../DocumentView'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { ImageField } from '../../../../fields/URLField'; +import { resolve } from 'url'; +import { DocData } from '../../../../fields/DocSymbols'; + +interface GenerativeFillProps { + imageEditorOpen: boolean; + imageEditorSource: string; + imageRootDoc: Doc | undefined; + addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined; +} + +// Added field on image doc: gen_fill_children: List of children Docs + +const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => { + const canvasRef = useRef(null); + const canvasBackgroundRef = useRef(null); + const drawingAreaRef = useRef(null); + const [cursorData, setCursorData] = useState({ + 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.ADD); + const [input, setInput] = useState(''); + const [loading, setLoading] = useState(false); + const [canvasDims, setCanvasDims] = useState({ + width: canvasSize, + height: canvasSize, + }); + // whether to create a new collection or not + const [isNewCollection, setIsNewCollection] = useState(true); + // the current image in the main canvas + const currImg = useRef(null); + // the unedited version of each generation (parent) + const originalImg = useRef(null); + const originalDoc = useRef(null); + // stores history of data urls + const undoStack = useRef([]); + // stores redo stack + const redoStack = useRef([]); + + // references to keep track of tree structure + const newCollectionRef = useRef(null); + const parentDoc = useRef(null); + const childrenDocs = useRef([]); + + // constants for image cutting + const cutPts = useRef([]); + + /** + * + * @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 => { + 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(parentList); + } else { + parentDoc.current.gen_fill_children = new List([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(genFillTool); + + // the top controls for making a new collection, resetting, and applying edits, + function renderControls() { + return ( +
+

Image Editor

+ {/* } /> */} +
+ { + setIsNewCollection(prev => !prev); + }} + /> + } + label="Create New Collection" + labelPlacement="end" + sx={{ whiteSpace: 'nowrap' }} + /> + + } onClick={handleViewClose} /> +
+
+ ); + } + + // the side icons including tool type, the slider, and undo/redo + function renderSideIcons() { + return ( +
+
+ {imageEditTools.map(tool => { + return ImageToolButton(tool, tool.type === currTool.type, changeTool); + })} +
+
e.stopPropagation()}> + {currTool.type === ImageToolType.GenerativeFill && ( + { + setCursorData(prev => ({ ...prev, width: val as number })); + }} + /> + )} + {currTool.type === ImageToolType.Cut && ( + { + setCursorData(prev => ({ ...prev, width: val as number })); + }} + /> + )} +
+ {/* Undo and Redo */} +
+ { + e.stopPropagation(); + handleUndo(); + }} + onPointerUp={e => { + e.stopPropagation(); + }} + color={activeColor} + tooltip="Undo" + icon={} + /> + { + e.stopPropagation(); + handleRedo(); + }} + onPointerUp={e => { + e.stopPropagation(); + }} + color={activeColor} + tooltip="Redo" + icon={} + /> +
+
+ ); + } + + // circular pointer for drawing/erasing + function renderPointer() { + return ( +
+
+
+ ); + } + + // the previews for each edit + function renderEditThumbnails() { + return ( +
+ {edits.map((edit, i) => ( + image edits { + 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 && ( +
+ + image stuff { + 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; + }} + /> +
+ )} +
+ ); + } + + // the prompt box for generative fill + function renderPromptBox() { + return ( +
+ 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)', + }} + /> +
+ ); + } + + return ( +
+ {renderControls()} + {/* Main canvas for editing */} +
+ + + {renderPointer()} + {renderSideIcons()} + {renderEditThumbnails()} +
+ {currTool.type === ImageToolType.GenerativeFill && renderPromptBox()} +
+ ); +}; + +export default ImageEditor; diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx new file mode 100644 index 000000000..e90babf9b --- /dev/null +++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx @@ -0,0 +1,69 @@ +import './GenerativeFillButtons.scss'; +import * as React from 'react'; +import ReactLoading from 'react-loading'; +import { Button, IconButton, Type } from 'browndash-components'; +import { AiOutlineInfo } from 'react-icons/ai'; +import { bgColor } from './imageEditorUtils/imageEditorConstants'; +import { ImageEditTool, ImageToolType } from './imageEditorUtils/imageEditorInterfaces'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { SettingsManager } from '../../../util/SettingsManager'; + +interface ButtonContainerProps { + onClick: () => Promise; + loading: boolean; + onReset: () => void; + btnText: string; +} + +export function ApplyFuncButtons({ loading, onClick: getEdit, onReset, btnText }: ButtonContainerProps) { + return ( +
+
+ ); +} + +export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTool: (type: ImageToolType) => void) { + return ( +
+
+ ); +} diff --git a/src/client/views/nodes/imageEditor/imageEditingUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditingUtils/ImageHandler.ts new file mode 100644 index 000000000..514e8a94f --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageEditingUtils/ImageHandler.ts @@ -0,0 +1,312 @@ +import { RefObject } from 'react'; +import { bgColor, canvasSize } from '../ImageEditorUtils/imageEditorConstants'; + +export interface APISuccess { + status: 'success'; + urls: string[]; +} + +export interface APIError { + status: 'error'; + message: string; +} + +export class ImageUtility { + /** + * + * @param canvas Canvas to convert + * @returns Blob of canvas + */ + static canvasToBlob = (canvas: HTMLCanvasElement): Promise => + new Promise(resolve => { + canvas.toBlob(blob => { + if (blob) { + resolve(blob); + } + }, 'image/png'); + }); + + // given a square api image, get the cropped img + static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => { + // Create a new canvas element + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (ctx) { + // Clear the canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + if (width < height) { + // horizontal padding, x offset + const xOffset = (canvasSize - width) / 2; + ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - height) / 2; + ctx.drawImage(img, 0, yOffset, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); + } + return canvas; + } + return undefined; + }; + + // converts an image to a canvas data url + static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise => + new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + const canvas = this.getCroppedImg(img, width, height); + if (canvas) { + const dataUrl = canvas.toDataURL(); + resolve(dataUrl); + } + }; + img.onerror = error => { + reject(error); + }; + img.src = imageSrc; + }); + + // calls the openai api to get image edits + static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise => { + const apiUrl = 'https://api.openai.com/v1/images/edits'; + const fd = new FormData(); + fd.append('image', imgBlob, 'image.png'); + fd.append('mask', maskBlob, 'mask.png'); + fd.append('prompt', prompt); + fd.append('size', '1024x1024'); + fd.append('n', n ? JSON.stringify(n) : '1'); + fd.append('response_format', 'b64_json'); + + try { + const res = await fetch(apiUrl, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.OPENAI_KEY}`, + }, + body: fd, + }); + const data = await res.json(); + console.log(data.data); + return { + status: 'success', + urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`), + }; + } catch (err) { + console.log(err); + return { status: 'error', message: 'API error.' }; + } + }; + + // mock api call + static mockGetEdit = async (mockSrc: string): Promise => ({ + status: 'success', + urls: [mockSrc, mockSrc, mockSrc], + }); + + // Gets the canvas rendering context of a canvas + static getCanvasContext = (canvasRef: RefObject): CanvasRenderingContext2D | null => { + if (!canvasRef.current) return null; + const ctx = canvasRef.current.getContext('2d'); + if (!ctx) return null; + return ctx; + }; + + // Helper for downloading the canvas (for debugging) + static downloadCanvas = (canvas: HTMLCanvasElement) => { + const url = canvas.toDataURL(); + const downloadLink = document.createElement('a'); + downloadLink.href = url; + downloadLink.download = 'canvas'; + + downloadLink.click(); + downloadLink.remove(); + }; + + // Download the canvas (for debugging) + static downloadImageCanvas = (imgUrl: string) => { + const img = new Image(); + img.src = imgUrl; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + ctx?.drawImage(img, 0, 0, canvasSize, canvasSize); + + this.downloadCanvas(canvas); + }; + }; + + // Clears the canvas + static clearCanvas = (canvasRef: React.RefObject) => { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx || !canvasRef.current) return; + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + }; + + // Draws the image to the current canvas + static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject, width: number, height: number) => { + const drawImg = (htmlImg: HTMLImageElement) => { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx) return; + ctx.globalCompositeOperation = 'source-over'; + ctx.clearRect(0, 0, width, height); + ctx.drawImage(htmlImg, 0, 0, width, height); + }; + + if (img.complete) { + drawImg(img); + } else { + img.onload = () => { + drawImg(img); + }; + } + }; + + // Gets the image mask for the openai endpoint + static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + if (!ctx) return undefined; + ctx?.clearRect(0, 0, canvasSize, canvasSize); + ctx.drawImage(paddedCanvas, 0, 0); + + // extract and set padding data + if (srcCanvas.height > srcCanvas.width) { + // horizontal padding, x offset + const xOffset = (canvasSize - srcCanvas.width) / 2; + ctx?.clearRect(xOffset, 0, srcCanvas.width, srcCanvas.height); + ctx.drawImage(srcCanvas, xOffset, 0, srcCanvas.width, srcCanvas.height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - srcCanvas.height) / 2; + ctx?.clearRect(0, yOffset, srcCanvas.width, srcCanvas.height); + ctx.drawImage(srcCanvas, 0, yOffset, srcCanvas.width, srcCanvas.height); + } + return canvas; + }; + + // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) + static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const { data } = imageData; + for (let i = 0; i < canvas.height; i++) { + for (let j = 0; j < xOffset; j++) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceI = i; + const sourceJ = xOffset + (xOffset - j); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + for (let i = 0; i < canvas.height; i++) { + for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceI = i; + const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j)); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + ctx.putImageData(imageData, 0, 0); + }; + + // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) + static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const { data } = imageData; + for (let j = 0; j < canvas.width; j++) { + for (let i = 0; i < yOffset; i++) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceJ = j; + const sourceI = yOffset + (yOffset - i); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + for (let j = 0; j < canvas.width; j++) { + for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceJ = j; + const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i)); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + ctx.putImageData(imageData, 0, 0); + }; + + // Gets the unaltered (besides filling in padding) version of the image for the api call + static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + if (!ctx) return undefined; + // fix scaling + const scale = Math.min(canvasSize / img.width, canvasSize / img.height); + const width = Math.floor(img.width * scale); + const height = Math.floor(img.height * scale); + ctx?.clearRect(0, 0, canvasSize, canvasSize); + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, canvasSize, canvasSize); + + // extract and set padding data + if (img.naturalHeight > img.naturalWidth) { + // horizontal padding, x offset + const xOffset = Math.floor((canvasSize - width) / 2); + ctx.drawImage(img, xOffset, 0, width, height); + + // draw reflected image padding + this.drawHorizontalReflection(ctx, canvas, xOffset); + } else { + // vertical padding, y offset + const yOffset = Math.floor((canvasSize - height) / 2); + ctx.drawImage(img, 0, yOffset, width, height); + + // draw reflected image padding + this.drawVerticalReflection(ctx, canvas, yOffset); + } + return canvas; + }; + + /** + * Converts a url to base64 (tainted canvas workaround) + */ + static urlToBase64 = async (imageUrl: string): Promise => { + try { + const res = await fetch(imageUrl); + const blob = await res.blob(); + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const base64Data = reader.result?.toString().split(',')[1]; + if (base64Data) { + resolve(base64Data); + } else { + reject(new Error('Failed to convert.')); + } + }; + reader.onerror = () => { + reject(new Error('Error reading image data')); + }; + reader.readAsDataURL(blob); + }); + } catch (err) { + console.error(err); + } + return undefined; + }; +} diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts new file mode 100644 index 000000000..a9fe02d4f --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/BrushHandler.ts @@ -0,0 +1,35 @@ +import { GenerativeFillMathHelpers } from './GenerativeFillMathHelpers'; +import { eraserColor } from './imageEditorConstants'; +import { Point } from './imageEditorInterfaces'; +import { points } from '@turf/turf'; + +export enum BrushType { + GEN_FILL, + CUT, +} + +export class BrushHandler { + static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => { + ctx.globalCompositeOperation = 'destination-out'; + ctx.fillStyle = fillColor; + ctx.shadowColor = eraserColor; + ctx.shadowBlur = 5; + ctx.beginPath(); + ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); + ctx.fill(); + ctx.closePath(); + }; + + static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, brushType: BrushType) => { + const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint); + const pts: Point[] = []; + for (let i = 0; i < dist; i += 5) { + const s = i / dist; + const x = startPoint.x * (1 - s) + endPoint.x * s; + const y = startPoint.y * (1 - s) + endPoint.y * s; + pts.push({ x: startPoint.x, y: startPoint.y }); + BrushHandler.brushCircleOverlay(x, y, brushRadius, ctx, fillColor); + } + return pts; + }; +} diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/GenerativeFillMathHelpers.ts new file mode 100644 index 000000000..f820300f3 --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/GenerativeFillMathHelpers.ts @@ -0,0 +1,6 @@ +import { Point } from './imageEditorInterfaces'; + +export class GenerativeFillMathHelpers { + static distanceBetween = (p1: Point, p2: Point) => Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2); + static angleBetween = (p1: Point, p2: Point) => Math.atan2(p2.x - p1.x, p2.y - p1.y); +} diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts new file mode 100644 index 000000000..ece0f4d7f --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/ImageHandler.ts @@ -0,0 +1,312 @@ +import { RefObject } from 'react'; +import { bgColor, canvasSize } from './imageEditorConstants'; + +export interface APISuccess { + status: 'success'; + urls: string[]; +} + +export interface APIError { + status: 'error'; + message: string; +} + +export class ImageUtility { + /** + * + * @param canvas Canvas to convert + * @returns Blob of canvas + */ + static canvasToBlob = (canvas: HTMLCanvasElement): Promise => + new Promise(resolve => { + canvas.toBlob(blob => { + if (blob) { + resolve(blob); + } + }, 'image/png'); + }); + + // given a square api image, get the cropped img + static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => { + // Create a new canvas element + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (ctx) { + // Clear the canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + if (width < height) { + // horizontal padding, x offset + const xOffset = (canvasSize - width) / 2; + ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - height) / 2; + ctx.drawImage(img, 0, yOffset, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); + } + return canvas; + } + return undefined; + }; + + // converts an image to a canvas data url + static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise => + new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + const canvas = this.getCroppedImg(img, width, height); + if (canvas) { + const dataUrl = canvas.toDataURL(); + resolve(dataUrl); + } + }; + img.onerror = error => { + reject(error); + }; + img.src = imageSrc; + }); + + // calls the openai api to get image edits + static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise => { + const apiUrl = 'https://api.openai.com/v1/images/edits'; + const fd = new FormData(); + fd.append('image', imgBlob, 'image.png'); + fd.append('mask', maskBlob, 'mask.png'); + fd.append('prompt', prompt); + fd.append('size', '1024x1024'); + fd.append('n', n ? JSON.stringify(n) : '1'); + fd.append('response_format', 'b64_json'); + + try { + const res = await fetch(apiUrl, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.OPENAI_KEY}`, + }, + body: fd, + }); + const data = await res.json(); + console.log(data.data); + return { + status: 'success', + urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`), + }; + } catch (err) { + console.log(err); + return { status: 'error', message: 'API error.' }; + } + }; + + // mock api call + static mockGetEdit = async (mockSrc: string): Promise => ({ + status: 'success', + urls: [mockSrc, mockSrc, mockSrc], + }); + + // Gets the canvas rendering context of a canvas + static getCanvasContext = (canvasRef: RefObject): CanvasRenderingContext2D | null => { + if (!canvasRef.current) return null; + const ctx = canvasRef.current.getContext('2d'); + if (!ctx) return null; + return ctx; + }; + + // Helper for downloading the canvas (for debugging) + static downloadCanvas = (canvas: HTMLCanvasElement) => { + const url = canvas.toDataURL(); + const downloadLink = document.createElement('a'); + downloadLink.href = url; + downloadLink.download = 'canvas'; + + downloadLink.click(); + downloadLink.remove(); + }; + + // Download the canvas (for debugging) + static downloadImageCanvas = (imgUrl: string) => { + const img = new Image(); + img.src = imgUrl; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + ctx?.drawImage(img, 0, 0, canvasSize, canvasSize); + + this.downloadCanvas(canvas); + }; + }; + + // Clears the canvas + static clearCanvas = (canvasRef: React.RefObject) => { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx || !canvasRef.current) return; + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + }; + + // Draws the image to the current canvas + static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject, width: number, height: number) => { + const drawImg = (htmlImg: HTMLImageElement) => { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx) return; + ctx.globalCompositeOperation = 'source-over'; + ctx.clearRect(0, 0, canvasRef.current?.width || width, canvasRef.current?.height || height); + ctx.drawImage(htmlImg, 0, 0, width, height); + }; + + if (img.complete) { + drawImg(img); + } else { + img.onload = () => { + drawImg(img); + }; + } + }; + + // Gets the image mask for the openai endpoint + static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + if (!ctx) return undefined; + ctx?.clearRect(0, 0, canvasSize, canvasSize); + ctx.drawImage(paddedCanvas, 0, 0); + + // extract and set padding data + if (srcCanvas.height > srcCanvas.width) { + // horizontal padding, x offset + const xOffset = (canvasSize - srcCanvas.width) / 2; + ctx?.clearRect(xOffset, 0, srcCanvas.width, srcCanvas.height); + ctx.drawImage(srcCanvas, xOffset, 0, srcCanvas.width, srcCanvas.height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - srcCanvas.height) / 2; + ctx?.clearRect(0, yOffset, srcCanvas.width, srcCanvas.height); + ctx.drawImage(srcCanvas, 0, yOffset, srcCanvas.width, srcCanvas.height); + } + return canvas; + }; + + // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) + static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const { data } = imageData; + for (let i = 0; i < canvas.height; i++) { + for (let j = 0; j < xOffset; j++) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceI = i; + const sourceJ = xOffset + (xOffset - j); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + for (let i = 0; i < canvas.height; i++) { + for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceI = i; + const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j)); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + ctx.putImageData(imageData, 0, 0); + }; + + // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) + static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const { data } = imageData; + for (let j = 0; j < canvas.width; j++) { + for (let i = 0; i < yOffset; i++) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceJ = j; + const sourceI = yOffset + (yOffset - i); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + for (let j = 0; j < canvas.width; j++) { + for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceJ = j; + const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i)); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + ctx.putImageData(imageData, 0, 0); + }; + + // Gets the unaltered (besides filling in padding) version of the image for the api call + static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + if (!ctx) return undefined; + // fix scaling + const scale = Math.min(canvasSize / img.width, canvasSize / img.height); + const width = Math.floor(img.width * scale); + const height = Math.floor(img.height * scale); + ctx?.clearRect(0, 0, canvasSize, canvasSize); + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, canvasSize, canvasSize); + + // extract and set padding data + if (img.naturalHeight > img.naturalWidth) { + // horizontal padding, x offset + const xOffset = Math.floor((canvasSize - width) / 2); + ctx.drawImage(img, xOffset, 0, width, height); + + // draw reflected image padding + this.drawHorizontalReflection(ctx, canvas, xOffset); + } else { + // vertical padding, y offset + const yOffset = Math.floor((canvasSize - height) / 2); + ctx.drawImage(img, 0, yOffset, width, height); + + // draw reflected image padding + this.drawVerticalReflection(ctx, canvas, yOffset); + } + return canvas; + }; + + /** + * Converts a url to base64 (tainted canvas workaround) + */ + static urlToBase64 = async (imageUrl: string): Promise => { + try { + const res = await fetch(imageUrl); + const blob = await res.blob(); + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const base64Data = reader.result?.toString().split(',')[1]; + if (base64Data) { + resolve(base64Data); + } else { + reject(new Error('Failed to convert.')); + } + }; + reader.onerror = () => { + reject(new Error('Error reading image data')); + }; + reader.readAsDataURL(blob); + }); + } catch (err) { + console.error(err); + } + return undefined; + }; +} diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/PointerHandler.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/PointerHandler.ts new file mode 100644 index 000000000..e86f46636 --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/PointerHandler.ts @@ -0,0 +1,11 @@ +import { Point } from './imageEditorInterfaces'; + +export class PointerHandler { + static getPointRelativeToElement = (element: HTMLElement, e: React.PointerEvent | PointerEvent, scale: number): Point => { + const boundingBox = element.getBoundingClientRect(); + return { + x: (e.clientX - boundingBox.x) / scale, + y: (e.clientY - boundingBox.y) / scale, + }; + }; +} diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts new file mode 100644 index 000000000..4772304bc --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorConstants.ts @@ -0,0 +1,9 @@ +export const canvasSize = 1024; +export const freeformRenderSize = 300; +export const offsetDistanceY = freeformRenderSize + 400; +export const offsetX = 200; +export const newCollectionSize = 500; + +export const activeColor = '#1976d2'; +export const eraserColor = '#e1e9ec'; +export const bgColor = '#f0f4f6'; diff --git a/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts new file mode 100644 index 000000000..f4ae7d9c4 --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageEditorUtils/imageEditorInterfaces.ts @@ -0,0 +1,38 @@ +import { IconProp } from '@fortawesome/fontawesome-svg-core'; + +export interface CursorData { + x: number; + y: number; + width: number; +} + +export interface Point { + x: number; + y: number; +} + +export enum ImageToolType { + GenerativeFill = 'genFill', + Cut = 'cut', +} + +export interface ImageEditTool { + type: ImageToolType; + name: string; + btnText: string; + icon: IconProp; + applyFunc: () => Promise; + sliderMin?: number; + sliderMax?: number; + sliderDefault?: number; +} + +export enum BrushMode { + ADD, + SUBTRACT, +} + +export interface ImageDimensions { + width: number; + height: number; +} diff --git a/src/client/views/nodes/imageEditor/imageToolUtils/BrushHandler.ts b/src/client/views/nodes/imageEditor/imageToolUtils/BrushHandler.ts new file mode 100644 index 000000000..7139bebc3 --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageToolUtils/BrushHandler.ts @@ -0,0 +1,35 @@ +import { GenerativeFillMathHelpers } from '../imageEditorUtils/GenerativeFillMathHelpers'; +import { eraserColor } from '../imageEditorUtils/imageEditorConstants'; +import { Point } from '../imageEditorUtils/imageEditorInterfaces'; +import { points } from '@turf/turf'; + +export enum BrushType { + GEN_FILL, + CUT, +} + +export class BrushHandler { + static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => { + ctx.globalCompositeOperation = 'destination-out'; + ctx.fillStyle = fillColor; + ctx.shadowColor = eraserColor; + ctx.shadowBlur = 5; + ctx.beginPath(); + ctx.arc(x, y, brushRadius, 0, 2 * Math.PI); + ctx.fill(); + ctx.closePath(); + }; + + static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, brushType: BrushType) => { + const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint); + const pts: Point[] = []; + for (let i = 0; i < dist; i += 5) { + const s = i / dist; + const x = startPoint.x * (1 - s) + endPoint.x * s; + const y = startPoint.y * (1 - s) + endPoint.y * s; + pts.push({ x: startPoint.x, y: startPoint.y }); + BrushHandler.brushCircleOverlay(x, y, brushRadius, ctx, fillColor); + } + return pts; + }; +} diff --git a/src/client/views/nodes/imageEditor/imageToolUtils/ImageHandler.ts b/src/client/views/nodes/imageEditor/imageToolUtils/ImageHandler.ts new file mode 100644 index 000000000..b9723b5be --- /dev/null +++ b/src/client/views/nodes/imageEditor/imageToolUtils/ImageHandler.ts @@ -0,0 +1,312 @@ +import { RefObject } from 'react'; +import { bgColor, canvasSize } from '../imageEditorUtils/imageEditorConstants'; + +export interface APISuccess { + status: 'success'; + urls: string[]; +} + +export interface APIError { + status: 'error'; + message: string; +} + +export class ImageUtility { + /** + * + * @param canvas Canvas to convert + * @returns Blob of canvas + */ + static canvasToBlob = (canvas: HTMLCanvasElement): Promise => + new Promise(resolve => { + canvas.toBlob(blob => { + if (blob) { + resolve(blob); + } + }, 'image/png'); + }); + + // given a square api image, get the cropped img + static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => { + // Create a new canvas element + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (ctx) { + // Clear the canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + if (width < height) { + // horizontal padding, x offset + const xOffset = (canvasSize - width) / 2; + ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - height) / 2; + ctx.drawImage(img, 0, yOffset, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height); + } + return canvas; + } + return undefined; + }; + + // converts an image to a canvas data url + static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise => + new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + const canvas = this.getCroppedImg(img, width, height); + if (canvas) { + const dataUrl = canvas.toDataURL(); + resolve(dataUrl); + } + }; + img.onerror = error => { + reject(error); + }; + img.src = imageSrc; + }); + + // calls the openai api to get image edits + static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise => { + const apiUrl = 'https://api.openai.com/v1/images/edits'; + const fd = new FormData(); + fd.append('image', imgBlob, 'image.png'); + fd.append('mask', maskBlob, 'mask.png'); + fd.append('prompt', prompt); + fd.append('size', '1024x1024'); + fd.append('n', n ? JSON.stringify(n) : '1'); + fd.append('response_format', 'b64_json'); + + try { + const res = await fetch(apiUrl, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.OPENAI_KEY}`, + }, + body: fd, + }); + const data = await res.json(); + console.log(data.data); + return { + status: 'success', + urls: (data.data as { b64_json: string }[]).map(urlData => `data:image/png;base64,${urlData.b64_json}`), + }; + } catch (err) { + console.log(err); + return { status: 'error', message: 'API error.' }; + } + }; + + // mock api call + static mockGetEdit = async (mockSrc: string): Promise => ({ + status: 'success', + urls: [mockSrc, mockSrc, mockSrc], + }); + + // Gets the canvas rendering context of a canvas + static getCanvasContext = (canvasRef: RefObject): CanvasRenderingContext2D | null => { + if (!canvasRef.current) return null; + const ctx = canvasRef.current.getContext('2d'); + if (!ctx) return null; + return ctx; + }; + + // Helper for downloading the canvas (for debugging) + static downloadCanvas = (canvas: HTMLCanvasElement) => { + const url = canvas.toDataURL(); + const downloadLink = document.createElement('a'); + downloadLink.href = url; + downloadLink.download = 'canvas'; + + downloadLink.click(); + downloadLink.remove(); + }; + + // Download the canvas (for debugging) + static downloadImageCanvas = (imgUrl: string) => { + const img = new Image(); + img.src = imgUrl; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + ctx?.drawImage(img, 0, 0, canvasSize, canvasSize); + + this.downloadCanvas(canvas); + }; + }; + + // Clears the canvas + static clearCanvas = (canvasRef: React.RefObject) => { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx || !canvasRef.current) return; + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + }; + + // Draws the image to the current canvas + static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject, width: number, height: number) => { + const drawImg = (htmlImg: HTMLImageElement) => { + const ctx = this.getCanvasContext(canvasRef); + if (!ctx) return; + ctx.globalCompositeOperation = 'source-over'; + ctx.clearRect(0, 0, width, height); + ctx.drawImage(htmlImg, 0, 0, width, height); + }; + + if (img.complete) { + drawImg(img); + } else { + img.onload = () => { + drawImg(img); + }; + } + }; + + // Gets the image mask for the openai endpoint + static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + if (!ctx) return undefined; + ctx?.clearRect(0, 0, canvasSize, canvasSize); + ctx.drawImage(paddedCanvas, 0, 0); + + // extract and set padding data + if (srcCanvas.height > srcCanvas.width) { + // horizontal padding, x offset + const xOffset = (canvasSize - srcCanvas.width) / 2; + ctx?.clearRect(xOffset, 0, srcCanvas.width, srcCanvas.height); + ctx.drawImage(srcCanvas, xOffset, 0, srcCanvas.width, srcCanvas.height); + } else { + // vertical padding, y offset + const yOffset = (canvasSize - srcCanvas.height) / 2; + ctx?.clearRect(0, yOffset, srcCanvas.width, srcCanvas.height); + ctx.drawImage(srcCanvas, 0, yOffset, srcCanvas.width, srcCanvas.height); + } + return canvas; + }; + + // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) + static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const { data } = imageData; + for (let i = 0; i < canvas.height; i++) { + for (let j = 0; j < xOffset; j++) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceI = i; + const sourceJ = xOffset + (xOffset - j); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + for (let i = 0; i < canvas.height; i++) { + for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceI = i; + const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j)); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + ctx.putImageData(imageData, 0, 0); + }; + + // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas) + static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const { data } = imageData; + for (let j = 0; j < canvas.width; j++) { + for (let i = 0; i < yOffset; i++) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceJ = j; + const sourceI = yOffset + (yOffset - i); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + for (let j = 0; j < canvas.width; j++) { + for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) { + const targetIdx = 4 * (i * canvas.width + j); + const sourceJ = j; + const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i)); + const sourceIdx = 4 * (sourceI * canvas.width + sourceJ); + data[targetIdx] = data[sourceIdx]; + data[targetIdx + 1] = data[sourceIdx + 1]; + data[targetIdx + 2] = data[sourceIdx + 2]; + } + } + ctx.putImageData(imageData, 0, 0); + }; + + // Gets the unaltered (besides filling in padding) version of the image for the api call + static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => { + const canvas = document.createElement('canvas'); + canvas.width = canvasSize; + canvas.height = canvasSize; + const ctx = canvas.getContext('2d'); + if (!ctx) return undefined; + // fix scaling + const scale = Math.min(canvasSize / img.width, canvasSize / img.height); + const width = Math.floor(img.width * scale); + const height = Math.floor(img.height * scale); + ctx?.clearRect(0, 0, canvasSize, canvasSize); + ctx.fillStyle = bgColor; + ctx.fillRect(0, 0, canvasSize, canvasSize); + + // extract and set padding data + if (img.naturalHeight > img.naturalWidth) { + // horizontal padding, x offset + const xOffset = Math.floor((canvasSize - width) / 2); + ctx.drawImage(img, xOffset, 0, width, height); + + // draw reflected image padding + this.drawHorizontalReflection(ctx, canvas, xOffset); + } else { + // vertical padding, y offset + const yOffset = Math.floor((canvasSize - height) / 2); + ctx.drawImage(img, 0, yOffset, width, height); + + // draw reflected image padding + this.drawVerticalReflection(ctx, canvas, yOffset); + } + return canvas; + }; + + /** + * Converts a url to base64 (tainted canvas workaround) + */ + static urlToBase64 = async (imageUrl: string): Promise => { + try { + const res = await fetch(imageUrl); + const blob = await res.blob(); + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const base64Data = reader.result?.toString().split(',')[1]; + if (base64Data) { + resolve(base64Data); + } else { + reject(new Error('Failed to convert.')); + } + }; + reader.onerror = () => { + reject(new Error('Error reading image data')); + }; + reader.readAsDataURL(blob); + }); + } catch (err) { + console.error(err); + } + return undefined; + }; +} diff --git a/src/client/views/smartdraw/AnnotationPalette.tsx b/src/client/views/smartdraw/AnnotationPalette.tsx index f1e2e4f41..dc0c01d36 100644 --- a/src/client/views/smartdraw/AnnotationPalette.tsx +++ b/src/client/views/smartdraw/AnnotationPalette.tsx @@ -185,7 +185,7 @@ export class AnnotationPalette extends ObservableReactComponent Date: Fri, 22 Nov 2024 10:27:33 -0500 Subject: added initial Firefly endpoint and hanged smartDrawHandler to generate an image and an svg. --- src/client/util/bezierFit.ts | 3 +- src/client/views/MainView.tsx | 32 ++++++++++++-- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 3 +- src/client/views/pdf/AnchorMenu.tsx | 9 ++-- src/client/views/smartdraw/SmartDrawHandler.tsx | 29 ++++++------ src/server/ApiManagers/DataVizManager.ts | 2 +- src/server/ApiManagers/FireflyManager.ts | 51 ++++++++++++++++++++++ src/server/DashUploadUtils.ts | 3 +- src/server/index.ts | 3 +- webpack.config.js | 7 ++- 10 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 src/server/ApiManagers/FireflyManager.ts (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts index d52460023..84b27e84c 100644 --- a/src/client/util/bezierFit.ts +++ b/src/client/util/bezierFit.ts @@ -703,7 +703,6 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) }); coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) }); coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) }); - coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) }); lastPt = { X: parseInt(match[3]), Y: parseInt(match[4]) }; } else if (match[0].startsWith('C')) { coordList.push({ X: parseInt(match[5]), Y: parseInt(match[6]) }); @@ -720,7 +719,7 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { } }); const hasZ = attributes.d.match(/Z/); - if (hasZ) { + if (hasZ || attributes.fill) { coordList.push(lastPt); coordList.push(startPt); coordList.push(startPt); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7779d339f..0d071fe4f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import '../../../node_modules/browndash-components/dist/styles/global.min.css'; -import { ClientUtils, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; +import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, GetDocFromUrl, Opt, returnEmptyDoclist } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; @@ -1023,10 +1023,36 @@ export class MainView extends ObservableReactComponent { {[ ...SnappingManager.HorizSnapLines.map(l => ( - + )), ...SnappingManager.VertSnapLines.map(l => ( - + )), ]} diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index a61705250..3ef6bdd8b 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -431,7 +431,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { doc = Docs.Create.FunctionPlotDocument([], options); break; case 'dataviz': - case 'data_viz': + case 'data_viz': { const { fileUrl, id } = await Networking.PostToServer('/createCSV', { filename: (options.title as string).replace(/\s+/g, '') + '.csv', data: data, @@ -439,6 +439,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { doc = Docs.Create.DataVizDocument(fileUrl, { ...options, text: RTFCast(data) }); this.addCSVForAnalysis(doc, id); break; + } case 'chat': doc = Docs.Create.ChatDocument(options); break; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 5ab9b556c..fe03f32a5 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -131,12 +131,15 @@ export class AnchorMenu extends AntimodeMenu { /** * Creates a GPT drawing based on selected text. */ - gptDraw = async (e: React.PointerEvent) => { + gptDraw = (e: React.PointerEvent) => { try { SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation; runInAction(() => (this._isLoading = true)); - await SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true); - runInAction(() => (this._isLoading = false)); + SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true)?.then( + action(() => { + this._isLoading = false; + }) + ); } catch (err) { console.error(err); } diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index d0f6566a5..342b91bd9 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -13,6 +13,7 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkData, InkField, InkTool } from '../../../fields/InkField'; import { BoolCast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { Networking } from '../../Network'; import { GPTCallType, gptAPICall, gptDrawingColor } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { SettingsManager } from '../../util/SettingsManager'; @@ -21,7 +22,8 @@ import { SVGToBezier, SVGType } from '../../util/bezierFit'; import { InkingStroke } from '../InkingStroke'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { MarqueeView } from '../collections/collectionFreeForm'; -import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkDash, ActiveInkFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView'; +import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkBezierApprox, ActiveInkColor, ActiveInkDash, ActiveInkFillColor, ActiveInkWidth, ActiveIsInkMask, DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; +import { OpenWhere } from '../nodes/OpenWhere'; import './SmartDrawHandler.scss'; export interface DrawingOptions { @@ -230,20 +232,21 @@ export class SmartDrawHandler extends ObservableReactComponent { * Calls GPT API to create a drawing based on user input. */ @action - drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => { - if (input === '') return; - this._lastInput = { text: input, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y }; - const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true); - if (!res) { - console.error('GPT call failed'); - return; - } - const strokeData = await this.parseSvg(res, startPt, false, autoColor); - const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); - drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + drawWithGPT = (startPt: { X: number; Y: number }, prompt: string, complexity: number, size: number, autoColor: boolean) => { + if (prompt === '') return; + this._lastInput = { text: prompt, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y }; + + Networking.PostToServer('/queryFireflyImage', { prompt }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, { title: prompt }), OpenWhere.addRight)); + + const result = gptAPICall(`"${prompt}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true).then(res => + this.parseSvg(res, startPt, false, autoColor).then(strokeData => { + const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); + drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + }) + ); this._errorOccurredOnce = false; - return strokeData; + return result; }; /** diff --git a/src/server/ApiManagers/DataVizManager.ts b/src/server/ApiManagers/DataVizManager.ts index 88f22992d..d2028f23b 100644 --- a/src/server/ApiManagers/DataVizManager.ts +++ b/src/server/ApiManagers/DataVizManager.ts @@ -9,7 +9,7 @@ export default class DataVizManager extends ApiManager { register({ method: Method.GET, subscription: '/csvData', - secureHandler: async ({ req, res }) => { + secureHandler: ({ req, res }) => { const uri = req.query.uri as string; return new Promise(resolve => { diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts new file mode 100644 index 000000000..04fa8f065 --- /dev/null +++ b/src/server/ApiManagers/FireflyManager.ts @@ -0,0 +1,51 @@ +import { DashUploadUtils } from '../DashUploadUtils'; +import { _invalid, _success, Method } from '../RouteManager'; +import ApiManager, { Registration } from './ApiManager'; + +export default class FireflyManager extends ApiManager { + askFirefly = (prompt: string = 'a realistic illustration of a cat coding') => { + const fetched = fetch('https://ims-na1.adobelogin.com/ims/token/v3', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `grant_type=client_credentials&client_id=${process.env._CLIENT_FIREFLY_CLIENT_ID}&client_secret=${process.env._CLIENT_FIREFLY_SECRET}&scope=openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis`, + }) + .then(response => response.json()) + .then((data: { access_token: string }) => + fetch('https://firefly-api.adobe.io/v3/images/generate', { + method: 'POST', + headers: [ + ['Content-Type', 'application/json'], + ['Accept', 'application/json'], + ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], + ['Authorization', `Bearer ${data.access_token}`], + ], + body: `{ "prompt": "${prompt}" }`, + }) + .then(response => response.json().then(json => JSON.stringify((json.outputs?.[0] as { image: { url: string } })?.image))) + .catch(error => { + console.error('Error:', error); + return ''; + }) + ) + .catch(error => { + console.error('Error:', error); + return ''; + }); + return fetched; + }; + protected initialize(register: Registration): void { + register({ + method: Method.POST, + subscription: '/queryFireflyImage', + secureHandler: ({ req, res }) => + this.askFirefly(req.body.prompt).then(fire => + DashUploadUtils.UploadImage(JSON.parse(fire).url).then(info => { + if (info instanceof Error) _invalid(res, info.message); + else _success(res, info.accessPaths.agnostic.client); + }) + ), + }); + } +} diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 1e55a885a..032d13d43 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -369,7 +369,8 @@ export namespace DashUploadUtils { */ export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename: string, prefix = '', cleanUp = true): Promise => { const { requestable, source, ...remaining } = metadata; - const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split('/')[1].toLowerCase()}`; + const dfltSuffix = remaining.contentType.split('/')[1].toLowerCase(); + const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${dfltSuffix === 'xml' ? 'jpg' : dfltSuffix}`; const { images } = Directory; const information: Upload.ImageInformation = { accessPaths: { diff --git a/src/server/index.ts b/src/server/index.ts index 88dbd232d..1f9af9ee0 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -7,6 +7,7 @@ import AssistantManager from './ApiManagers/AssistantManager'; import DataVizManager from './ApiManagers/DataVizManager'; import DeleteManager from './ApiManagers/DeleteManager'; import DownloadManager from './ApiManagers/DownloadManager'; +import FireflyManager from './ApiManagers/FireflyManager'; import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager'; import SessionManager from './ApiManagers/SessionManager'; import UploadManager from './ApiManagers/UploadManager'; @@ -71,6 +72,7 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage new GeneralGoogleManager(), /* new GooglePhotosManager(), */ new DataVizManager(), new AssistantManager(), + new FireflyManager(), ]; // initialize API Managers @@ -112,7 +114,6 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage }); const serve: PublicHandler = ({ req, res }) => { - // eslint-disable-next-line new-cap const detector = new mobileDetect(req.headers['user-agent'] || ''); const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html'; res.sendFile(path.join(__dirname, '../../deploy/' + filename)); diff --git a/webpack.config.js b/webpack.config.js index e1afc64e5..67417fb02 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); @@ -36,7 +36,6 @@ function transferEnvironmentVariables() { } const resolvedClientSide = Object.keys(parsed).reduce((mapping, envKey) => { if (envKey.startsWith(prefix)) { - // eslint-disable-next-line mapping[`process.env.${envKey.replace(prefix, '')}`] = JSON.stringify(parsed[envKey]); } return mapping; @@ -112,7 +111,7 @@ module.exports = { test: /\.scss|css$/, exclude: /\.module\.scss$/i, use: [ - { loader: 'style-loader' }, // eslint-disable-next-line prettier/prettier + { loader: 'style-loader' }, // { loader: 'css-loader' }, { loader: 'sass-loader' }, ], @@ -127,7 +126,7 @@ module.exports = { { test: /\.module\.scss$/i, use: [ - { loader: 'style-loader' }, // eslint-disable-next-line prettier/prettier + { loader: 'style-loader' }, // { loader: 'css-loader', options: { modules: true } }, { loader: 'sass-loader' }, ], -- cgit v1.2.3-70-g09d2 From cffb72e762d989eb1219a5736f082f5b656bee37 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts Date: Wed, 1 Jan 2025 22:45:48 -0800 Subject: Update MainView.tsx --- src/client/views/MainView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0d071fe4f..65d90a5a0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -7,7 +7,7 @@ import { action, computed, configure, makeObservable, observable, reaction, runI import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; -import '../../../node_modules/browndash-components/dist/styles/global.min.css'; +import '@dash/components/src/global/globalCssVariables.scss'; import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, GetDocFromUrl, Opt, returnEmptyDoclist } from '../../fields/Doc'; -- cgit v1.2.3-70-g09d2 From 6fa61343a3e63a61747bc7123810190057d0bfe4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Jan 2025 12:14:12 -0500 Subject: cleaned up filter icons. --- src/client/util/CurrentUserUtils.ts | 38 +++++++++++++++++----- src/client/views/MainView.tsx | 25 +++----------- .../views/collections/CollectionCardDeckView.tsx | 20 +++--------- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 34 +++++++------------ 4 files changed, 48 insertions(+), 69 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e806675aa..306914450 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -40,6 +40,7 @@ import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; import { DocumentView } from "../views/nodes/DocumentView"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; export interface Button { // DocumentOptions fields a button can set @@ -67,6 +68,21 @@ export interface Button { subMenu?: Button[]; } +// Not really necessary, but for now, all tags should start with a capital first letter +export type TagName = + // eslint-disable-next-line @typescript-eslint/no-unused-vars + T extends `${infer First}${infer Rest}` + ? First extends Uppercase + ? First extends Lowercase + ? never // If it's the same when uppercased and lowercased, it's not a letter. + : T // Otherwise, it's a valid capitalized string. + : never + : never; +export function ToTagName(key: string):"Tag"{ + return ((str => str[0].toUpperCase() + str.slice(1))(key.startsWith('#') ? key.substring(1) : key)) as "Tag"; +} + + export let resolvedPorts: { server: number, socket: number }; export class CurrentUserUtils { @@ -703,18 +719,22 @@ pie title Minerals in my tap water ]}, ] } + + static filterBtnDesc(tag:TagName|"Tag", icon:IconProp):Button { + return { title: tag, isSystem: false, icon: icon.toString(), toolTip:`Click to toggle visibility of ${tag} tagged Docs`, btnType: ButtonType.ToggleButton, expertMode: false, toolType:`#${tag.toLowerCase()}`, funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}} + } static filterTools(): Button[] { - const defaultTagButtonDescs = [ - { title: "Star", isSystem: false,icon: "star", toolTip:"Click to toggle visibility of Star tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#star", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, - { title: "Like", isSystem: false,icon: "heart", toolTip:"Click to toggle visibility of Like tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#like", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, - { title: "Todo", isSystem: false,icon: "bolt", toolTip:"Click to toggle visibility of Todo tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#todo", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, - { title: "Idea", isSystem: false,icon: "cloud", toolTip:"Click to toggle visibility of Idea tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#idea", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, + // If there's no active dashboard, then a default set of tags are added, otherwise, the user controls which tags are kept + const tagButtonDescs = Doc.UserDoc().activeDashboard ? [] : [ + this.filterBtnDesc("Star", "star"), + this.filterBtnDesc("Like", "heart"), + this.filterBtnDesc("Todo", "bolt"), + this.filterBtnDesc("Idea", "cloud") ]; - // hack: if there's no dashboard, create default filters. otherwise, just make sure that the Options button is preserved return [ - { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}}, - ...(Doc.UserDoc().activeDashboard ? [] : defaultTagButtonDescs) + { title:"Options",isSystem: true,icon: "gear", toolTip:"Click to customize list of filter buttons", btnType: ButtonType.ClickButton, expertMode: false, toolType:"-opts-",funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, false,_readOnly_);}'}}, + ...tagButtonDescs ] } static viewTools(): Button[] { @@ -866,7 +886,7 @@ pie title Minerals in my tap water childDontRegisterViews: true, flexGap: 0, _height: 30, _width: 30, ignoreClick: !params.scripts?.onClick, linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc}; - const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) ); + const items = (menuBtn?:Doc) => !menuBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtn) ); const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList; const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title), (opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7abca5197..d748b70ae 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -20,7 +20,7 @@ import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; import { CalendarManager } from '../util/CalendarManager'; import { CaptureManager } from '../util/CaptureManager'; -import { Button, CurrentUserUtils } from '../util/CurrentUserUtils'; +import { CurrentUserUtils, ToTagName } from '../util/CurrentUserUtils'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; @@ -63,7 +63,6 @@ import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; -import { ButtonType } from './nodes/FontIconBox/FontIconBox'; import { ImageEditorData as ImageEditor } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; @@ -874,25 +873,9 @@ export class MainView extends ObservableReactComponent { * @param hotKey tite of the new hotkey */ addHotKey = (hotKey: string) => { - const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); - const filter = DocCast(buttons.Filter); - const title = hotKey.startsWith('#') ? hotKey.substring(1) : hotKey; - - const newKey: Button = { - title, - icon: 'question', - toolTip: `Click to toggle the ${title}'s group's visibility`, - btnType: ButtonType.ToggleButton, - expertMode: false, - toolType: '#' + title, - funcs: {}, - scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' }, - }; - - const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter); - newBtn.isSystem = newBtn[DocData].isSystem = undefined; - - Doc.AddToFilterHotKeys(newBtn); + const filterIcons = DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter); + const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons); + Doc.AddToFilterHotKeys(menuDoc); }; @computed get mainInnerContent() { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 366d0f448..b40aa37de 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -462,24 +462,13 @@ export class CollectionCardView extends CollectionSubView() { case '6': doc.chatIndex = index; break; - case '1': { - const allHotKeys = Doc.MyFilterHotKeys; - let myTag = ''; + case '1': if (tag) { - for (let i = 0; i < allHotKeys.length; i++) { - const keyTag = StrCast(allHotKeys[i].toolType); - if (keyTag.includes(tag)) { - myTag = keyTag; - break; - } - } - - if (myTag) { - TagItem.addTagToDoc(doc, myTag); - } + const hashTag = tag.startsWith('#') ? tag : '#' + tag[0].toLowerCase() + tag.slice(1); + const filterTag = Doc.MyFilterHotKeys.map(key => StrCast(key.toolType)).find(key => key.includes(tag)) ?? hashTag; + TagItem.addTagToDoc(doc, filterTag); } break; - } case '2': case '4': doc.chatFilter = true; @@ -584,7 +573,6 @@ export class CollectionCardView extends CollectionSubView() { * Actually renders all the cards */ @computed get renderCards() { - console.log(this.docDraggedIndex, this.childDocs[0].title, this.childDocs[1].title); // Map sorted documents to their rendered components return this.childDocs.map((doc, index) => { const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 4028ef479..c33b81eb4 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -188,8 +188,8 @@ export class GPTPopup extends ObservableReactComponent { const selected = DocumentView.SelectedDocs().lastElement(); - const questionText = 'Question: ' + StrCast(selected['gptInputText']); - const rubricText = 'Rubric: ' + StrCast(selected['gptRubric']); + const questionText = 'Question: ' + StrCast(selected.gptInputText); + const rubricText = 'Rubric: ' + StrCast(selected.gptRubric); const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; try { @@ -262,27 +262,15 @@ export class GPTPopup extends ObservableReactComponent { try { const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE); - const questionNumber = questionType.split(' ')[0]; - let res = ''; - - switch (questionNumber) { - case '1': - case '2': - case '4': - res = await gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt); - break; - case '6': - res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); - break; - default: - { - const selected = DocumentView.SelectedDocs().lastElement(); - const questionText = StrCast(selected?.gptInputText); - - res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt); - } - break; - } + const questionNumber = questionType.split(' ')[0][0]; + const res = await (() => { + switch (questionNumber) { + case '1': + case '2': + case '4': return gptAPICall(this.sortDesc, GPTCallType.SUBSET, this.chatSortPrompt); + case '6': return gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); + default: return gptAPICall(StrCast(DocumentView.SelectedDocs().lastElement()?.gptInputText), GPTCallType.INFO, this.chatSortPrompt); + }})(); // prettier-ignore // Trigger the callback with the result if (this.onSortComplete) { -- cgit v1.2.3-70-g09d2 From cf791104c0b1608e37c3cf2d25dac7f6f58a1b66 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 18 Feb 2025 09:01:44 -0500 Subject: css fixes for gptPopup + close button --- src/client/util/SnappingManager.ts | 3 + src/client/views/MainView.tsx | 3 +- src/client/views/OverlayView.scss | 13 ++-- src/client/views/OverlayView.tsx | 18 ++++-- src/client/views/ScriptBox.tsx | 1 - src/client/views/global/globalScripts.ts | 8 +-- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/client/views/pdf/GPTPopup/GPTPopup.scss | 69 ++++++++++------------ src/client/views/pdf/GPTPopup/GPTPopup.tsx | 62 ++++++++++--------- 9 files changed, 98 insertions(+), 83 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index 2a150dc5a..9d8a41844 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -32,6 +32,7 @@ export class SnappingManager { @observable _hideDecorations: boolean = false; @observable _keepGestureMode: boolean = false; // for whether primitive selection enters a one-shot or persistent mode @observable _inkShape: Gestures | undefined = undefined; + @observable _chatVisible: boolean = false; private constructor() { SnappingManager._manager = this; @@ -66,6 +67,7 @@ export class SnappingManager { public static get HideDecorations(){ return this.Instance._hideDecorations; } // prettier-ignore public static get KeepGestureMode(){ return this.Instance._keepGestureMode; } // prettier-ignore public static get InkShape() { return this.Instance._inkShape; } // prettier-ignore + public static get ChatVisible() { return this.Instance._chatVisible; } // prettier-ignore public static SetLongPress = (press: boolean) => runInAction(() => {this.Instance._longPress = press}); // prettier-ignore public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore @@ -85,6 +87,7 @@ export class SnappingManager { public static SetHideDecorations= (state:boolean) =>runInAction(() => {this.Instance._hideDecorations = state}); // prettier-ignore public static SetKeepGestureMode= (state:boolean) =>runInAction(() => {this.Instance._keepGestureMode = state}); // prettier-ignore public static SetInkShape = (shape?:Gestures)=>runInAction(() => {this.Instance._inkShape = shape}); // prettier-ignore + public static SetChatVisible = (vis:boolean) =>runInAction(() => {this.Instance._chatVisible = vis}); // prettier-ignore public static userColor: string | undefined; public static userVariantColor: string | undefined; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d748b70ae..195b1c572 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -78,6 +78,7 @@ import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { SmartDrawHandler } from './smartdraw/SmartDrawHandler'; import { TopBar } from './topbar/TopBar'; +import { OverlayView } from './OverlayView'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @@ -168,6 +169,7 @@ export class MainView extends ObservableReactComponent { mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); componentDidMount() { + OverlayView.Instance.addWindow(, { x: 400, y: 200, width: 500, height: 400, title: 'GPT', backgroundColor: 'transparent', isHidden: () => !SnappingManager.ChatVisible, onClick: () => SnappingManager.SetChatVisible(false) }); // Utils.TraceConsoleLog(); reaction( // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection @@ -1154,7 +1156,6 @@ export class MainView extends ObservableReactComponent { {this.snapLines} - diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 33a297fd4..f4998efa1 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -4,7 +4,7 @@ top: 0; width: 100vw; height: 100vh; - z-index: 1001; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear + z-index: 2002; // shouold be greater than LightboxView's z-index so that link lines and the presentation mini player appear /* background-color: pink; */ user-select: none; } @@ -26,27 +26,30 @@ } .overlayWindow-titleBar { - flex: 0 1 30px; + flex: 0 1 20px; background: darkslategray; color: whitesmoke; text-align: center; cursor: move; + z-index: 1; } .overlayWindow-content { flex: 1 1 auto; display: flex; flex-direction: column; + z-index: 0; } .overlayWindow-closeButton { float: right; - height: 30px; - width: 30px; + height: 20px; + width: 20px; + padding: 0; + background-color: inherit; } .overlayWindow-resizeDragger { - background-color: rgb(0, 0, 0); position: absolute; right: 0px; bottom: 0px; diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 5e9677b45..20931fc3d 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent'; import './OverlayView.scss'; import { DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; +import { SnappingManager } from '../util/SnappingManager'; export type OverlayDisposer = () => void; @@ -27,12 +28,17 @@ export type OverlayElementOptions = { width?: number; height?: number; title?: string; + onClick?: (e: React.MouseEvent) => void; + isHidden?: () => boolean; + backgroundColor?: string; }; export interface OverlayWindowProps { children: JSX.Element; overlayOptions: OverlayElementOptions; - onClick: () => void; + onClick: (e: React.MouseEvent) => void; + isHidden?: () => boolean; + backgroundColor?: string; } @observer @@ -93,15 +99,17 @@ export class OverlayWindow extends ObservableReactComponent render() { return ( -
-
+
+
{this._props.overlayOptions.title || 'Untitled'}
{this.props.children}
-
+
); } @@ -166,7 +174,7 @@ export class OverlayView extends ObservableReactComponent { if (index !== -1) this._elements.splice(index, 1); }); const wincontents = ( - remove(wincontents)} key={Utils.GenerateGuid()} overlayOptions={options}> + remove(wincontents))} key={Utils.GenerateGuid()} overlayOptions={options}> {contents} ); diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 9c36e6d26..d05b0a6b6 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 790ebeabe..2bc0e3338 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -223,13 +223,13 @@ ScriptingGlobals.add(function showFreeform( setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = true; }, }], ['toggle-chat', { - checkResult: (doc: Doc) => GPTPopup.Instance.Visible, + checkResult: (doc: Doc) => SnappingManager.ChatVisible, setDoc: (doc: Doc, dv: DocumentView) => { - if (GPTPopup.Instance.Visible){ + if (SnappingManager.ChatVisible){ doc[Doc.LayoutFieldKey(doc)+"_sort"] = ''; - GPTPopup.Instance.setVisible(false); + SnappingManager.SetChatVisible(false); } else { - GPTPopup.Instance.setVisible(true); + SnappingManager.SetChatVisible(true); GPTPopup.Instance.setMode(GPTPopupMode.GPT_MENU); } }, diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 6960247e9..3abb39ff2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -977,7 +977,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.generateImage(), icon: 'star' }); + optionItems.push({ description: `Generate Dall-E Image`, event: this.generateImage, icon: 'star' }); // optionItems.push({ description: `Make AI Flashcards`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' }); optionItems.push({ description: `Ask GPT-3`, event: this.askGPT, icon: 'lightbulb' }); this._props.renderDepth && @@ -1061,7 +1061,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + generateImage = () => { GPTPopup.Instance?.setTextAnchor(this.getAnchor(false)); GPTPopup.Instance.generateImage((this.dataDoc.text as RichTextField)?.Text, this.Document, this._props.addDocument); }; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 9cf318dc0..0b832f64c 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -4,19 +4,23 @@ $greyborder: #d3d3d3; $lightgrey: #ececec; $button: #5b97ff; $highlightedText: #82e0ff; +$inputHeight: 60px; +$headingHeight: 32px; .gptPopup-summary-box { position: fixed; top: 115px; left: 75px; - width: 250px; - height: 200px; - min-height: 200px; - min-width: 180px; - + width: 100%; + height: 100%; + top: 0; + left: 0; + pointer-events: none; + border-top: solid gray 20px; border-radius: 16px; padding: 16px; padding-bottom: 0; + padding-top: 0px; z-index: 999; display: flex; flex-direction: column; @@ -24,17 +28,12 @@ $highlightedText: #82e0ff; background-color: #ffffff; box-shadow: 0 2px 5px #7474748d; color: $textgrey; - resize: both; /* Allows resizing */ - overflow: auto; - - .resize-handle { - width: 10px; - height: 10px; - background: #ccc; - position: absolute; - right: 0; - bottom: 0; - cursor: se-resize; + + .gptPopup-sortBox { + display: flex; + flex-direction: column; + height: calc(100% - $inputHeight - $headingHeight); + pointer-events: all; } .summary-heading { @@ -42,7 +41,7 @@ $highlightedText: #82e0ff; justify-content: space-between; align-items: center; border-bottom: 1px solid $greyborder; - padding-bottom: 5px; + height: $headingHeight; .summary-text { font-size: 12px; @@ -66,28 +65,17 @@ $highlightedText: #82e0ff; .gptPopup-content-wrapper { padding-top: 10px; min-height: 50px; - // max-height: 150px; - overflow-y: auto; - height: 100%; + height: calc(100% - 32px); } - .btns-wrapper-gpt { - height: 100%; + .inputWrapper { display: flex; justify-content: center; align-items: center; - flex-direction: column; - - .inputWrapper { - display: flex; - justify-content: center; - align-items: center; - height: 60px; - position: absolute; - bottom: 0; - width: 100%; - background-color: white; - } + height: $inputHeight; + background-color: white; + width: 100%; + pointer-events: all; .searchBox-input { height: 40px; @@ -97,14 +85,21 @@ $highlightedText: #82e0ff; border-color: #5b97ff; width: 90%; } + } + .btns-wrapper-gpt { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; .chat-wrapper { display: flex; flex-direction: column; width: 100%; - max-height: calc(100vh - 80px); + height: 100%; overflow-y: auto; - padding-bottom: 60px; + padding-right: 5px; } .chat-bubbles { @@ -194,7 +189,7 @@ $highlightedText: #82e0ff; .image-content-wrapper { display: flex; flex-direction: column; - align-items: flex-start; + align-items: center; gap: 8px; padding-bottom: 16px; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index f09d786d0..72381cfad 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { CgClose, CgCornerUpLeft } from 'react-icons/cg'; +import { CgCornerUpLeft } from 'react-icons/cg'; import ReactLoading from 'react-loading'; import { TypeAnimation } from 'react-type-animation'; import { ClientUtils } from '../../../../ClientUtils'; @@ -108,8 +108,6 @@ export class GPTPopup extends ObservableReactComponent { @observable private _mode: GPTPopupMode = GPTPopupMode.SUMMARY; @action public setMode = (mode: GPTPopupMode) => (this._mode = mode); - @observable public Visible: boolean = false; - @action public setVisible = (vis: boolean) => (this.Visible = vis); onQuizRandom?: () => void; onGptResponse?: (sortResult: string, questionType: GPTTypeStyle, tag?: string) => void; @@ -233,10 +231,10 @@ export class GPTPopup extends ObservableReactComponent { */ generateImage = (imgDesc: string, imgTarget: Doc, addToCollection?: (doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) => { this._imgTargetDoc = imgTarget; + SnappingManager.SetChatVisible(true); this.addDoc = addToCollection; this.setImgUrls([]); this.setMode(GPTPopupMode.IMAGE); - this.setVisible(true); this.setGptProcessing(true); this._imageDescription = imgDesc; @@ -259,8 +257,8 @@ export class GPTPopup extends ObservableReactComponent { * @param text the text to summarizz */ generateSummary = (text: string) => { + SnappingManager.SetChatVisible(true); this._textToSummarize = text; - this.setVisible(true); this.setMode(GPTPopupMode.SUMMARY); this.setGptProcessing(true); return gptAPICall(text, GPTCallType.SUMMARY) @@ -274,7 +272,6 @@ export class GPTPopup extends ObservableReactComponent { * this.dataJson in the popup. */ generateDataAnalysis = () => { - this.setVisible(true); this.setGptProcessing(true); return gptAPICall(this._dataJson, GPTCallType.DATA, this._dataChatPrompt) .then(res => { @@ -398,7 +395,7 @@ export class GPTPopup extends ObservableReactComponent { } }; - gptUserInput = (isUserPrompt: boolean) => ( + gptUserInput = () => (
@@ -412,6 +409,15 @@ export class GPTPopup extends ObservableReactComponent {
+ + ); + + promptBox = (isUserPrompt: boolean) => ( + <> +
+ {this.heading(isUserPrompt ? 'ASK' : 'QUIZ')} + {this.gptUserInput()} +
{ placeholder={`${isUserPrompt ? 'Have ChatGPT sort, tag, define, or filter your documents for you!' : 'Describe/answer the selected document!'}`} />
- + ); - promptBox = (isUserPrompt: boolean) => ( -
- {this.heading(isUserPrompt ? 'SORTING' : 'QUIZ')} - {this._mode === GPTPopupMode.GPT_MENU ? this.gptMenu() : this.gptUserInput(isUserPrompt)} + menuBox = () => ( +
+ {this.heading('CHOOSE')} + {this.gptMenu()}
); imageBox = () => ( -
+
{this.heading('GENERATED IMAGE')}
{this._imgUrls.map((rawSrc, i) => ( -
-
- dalle generation + <> +
+
+ dalle generation +
-
+ ))}
{this._gptProcessing ? null : ( @@ -461,7 +469,7 @@ export class GPTPopup extends ObservableReactComponent { summaryBox = () => ( <> -
+
{this.heading('SUMMARY')}
{!this._gptProcessing && @@ -481,7 +489,7 @@ export class GPTPopup extends ObservableReactComponent { {!this._gptProcessing && ( -
+
{this._stopAnimatingResponse ? ( <> this.generateSummary(this._textToSummarize + ' ')} icon={} color={StrCast(SettingsManager.userVariantColor)} /> @@ -571,19 +579,18 @@ export class GPTPopup extends ObservableReactComponent { ) : ( <> - {(this._mode === GPTPopupMode.USER_PROMPT || this._mode === GPTPopupMode.QUIZ_RESPONSE) && ( - } onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} style={{ right: '50px', position: 'absolute' }} /> - )} this._collectionContext && Doc.setDocFilter(this._collectionContext, 'tags', '#chat', 'remove')} /> - } onClick={() => this.setVisible(false)} /> + {(this._mode === GPTPopupMode.USER_PROMPT || this._mode === GPTPopupMode.QUIZ_RESPONSE) && ( + } onClick={() => (this._mode = GPTPopupMode.GPT_MENU)} /> + )} )} @@ -591,20 +598,19 @@ export class GPTPopup extends ObservableReactComponent { render() { return ( -
+
{(() => { //prettier-ignore switch (this._mode) { - case GPTPopupMode.GPT_MENU: - case GPTPopupMode.USER_PROMPT: + case GPTPopupMode.USER_PROMPT: case GPTPopupMode.QUIZ_RESPONSE: return this.promptBox(this._mode === GPTPopupMode.USER_PROMPT); + case GPTPopupMode.GPT_MENU: return this.menuBox(); case GPTPopupMode.SUMMARY: return this.summaryBox(); case GPTPopupMode.DATA: return this.dataAnalysisBox(); case GPTPopupMode.IMAGE: return this.imageBox(); default: return null; } })()} -
); } -- cgit v1.2.3-70-g09d2 From 515707c4561eb526426b8fa07dd50bd499fb91cc Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 25 Feb 2025 01:03:25 -0500 Subject: added a hideUI option to hide buttons. fixed a mess of runtime warnings mostly related to how scss files can be included in each other --- .../components/src/components/Button/Button.scss | 184 +++++------ .../src/components/ColorPicker/ColorPicker.scss | 36 +-- .../src/components/Dropdown/Dropdown.scss | 223 +++++++------- .../components/DropdownSearch/DropdownSearch.scss | 207 +++++++------ .../src/components/EditableText/EditableText.scss | 224 +++++++------- .../src/components/FormInput/FormInput.scss | 112 +++---- .../components/src/components/Group/Group.scss | 23 +- .../src/components/IconButton/IconButton.scss | 190 ++++++------ .../components/src/components/ListBox/ListBox.scss | 4 +- .../src/components/ListItem/ListItem.scss | 132 ++++---- .../components/src/components/Modal/Modal.scss | 74 ++--- .../src/components/MultiToggle/MultiToggle.scss | 6 +- .../components/NumberDropdown/NumberDropdown.scss | 7 +- .../src/components/NumberInput/NumberInput.scss | 4 +- .../components/src/components/Popup/Popup.scss | 40 +-- .../components/src/components/Slider/Slider.scss | 305 +++++++++---------- .../components/src/components/Slider/Slider.tsx | 338 +++++++++++---------- .../src/components/Template/Template.scss | 6 +- .../components/src/components/Toggle/Toggle.scss | 122 ++++---- .../components/src/global/globalCssVariables.scss | 110 +++---- src/client/util/CaptureManager.scss | 2 - src/client/util/CurrentUserUtils.ts | 14 +- src/client/util/SettingsManager.scss | 2 - src/client/util/SnappingManager.ts | 3 + src/client/util/reportManager/ReportManager.scss | 4 +- src/client/views/AntimodeMenu.scss | 6 +- src/client/views/ContextMenu.scss | 16 +- src/client/views/DashboardView.scss | 16 +- src/client/views/DocumentButtonBar.scss | 23 +- src/client/views/DocumentDecorations.scss | 38 +-- src/client/views/LightboxView.tsx | 4 +- src/client/views/Main.scss | 12 +- src/client/views/Main.tsx | 3 +- src/client/views/MainView.scss | 18 +- src/client/views/MainView.tsx | 8 +- src/client/views/PropertiesButtons.scss | 20 +- src/client/views/PropertiesSection.scss | 2 - src/client/views/PropertiesView.scss | 6 +- src/client/views/TemplateMenu.scss | 8 +- src/client/views/animationtimeline/Region.scss | 10 +- src/client/views/animationtimeline/Timeline.scss | 6 +- .../views/animationtimeline/TimelineMenu.scss | 16 +- .../views/animationtimeline/TimelineOverview.scss | 2 +- src/client/views/animationtimeline/Track.scss | 6 +- .../views/collections/CollectionCardDeckView.scss | 2 - .../collections/CollectionCarousel3DView.scss | 12 +- .../views/collections/CollectionDockingView.scss | 22 +- src/client/views/collections/CollectionMenu.scss | 6 +- .../collections/CollectionNoteTakingView.scss | 22 +- .../collections/CollectionStackedTimeline.scss | 26 +- .../views/collections/CollectionStackingView.scss | 22 +- .../views/collections/CollectionTreeView.scss | 10 +- src/client/views/collections/CollectionView.scss | 6 +- src/client/views/collections/TabDocView.scss | 2 +- src/client/views/collections/TreeView.scss | 6 +- .../collectionFreeForm/CollectionFreeFormView.scss | 16 +- .../collectionFreeForm/ImageLabelHandler.tsx | 14 +- .../collectionLinear/CollectionLinearView.scss | 22 +- .../collectionSchema/CollectionSchemaView.scss | 40 ++- src/client/views/linking/LinkMenu.scss | 2 +- src/client/views/linking/LinkMenuItem.scss | 8 +- .../views/newlightbox/ButtonMenu/ButtonMenu.scss | 12 +- .../views/newlightbox/ExploreView/ExploreView.scss | 48 +-- .../views/newlightbox/Header/LightboxHeader.scss | 28 +- .../RecommendationList/RecommendationList.scss | 179 ++++++----- .../components/EditableText/EditableText.scss | 46 +-- .../components/Recommendation/Recommendation.scss | 122 ++++---- .../components/SkeletonDoc/SkeletonDoc.scss | 133 ++++---- .../newlightbox/components/Template/Template.scss | 12 +- src/client/views/nodes/AudioBox.scss | 42 +-- .../views/nodes/DataVizBox/components/Chart.scss | 10 +- src/client/views/nodes/DocumentLinksButton.scss | 18 +- src/client/views/nodes/DocumentView.scss | 6 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/EquationBox.scss | 2 - .../views/nodes/FontIconBox/FontIconBox.scss | 36 +-- src/client/views/nodes/IconTagBox.scss | 4 +- src/client/views/nodes/KeyValueBox.scss | 22 +- src/client/views/nodes/KeyValuePair.scss | 2 +- src/client/views/nodes/LinkDescriptionPopup.scss | 10 +- src/client/views/nodes/MapBox/AnimationUtility.ts | 4 +- src/client/views/nodes/MapBox/MapBox.scss | 16 +- src/client/views/nodes/PDFBox.scss | 8 +- src/client/views/nodes/VideoBox.scss | 16 +- src/client/views/nodes/WebBox.scss | 4 +- .../nodes/chatbot/chatboxcomponents/ChatBox.scss | 11 +- .../views/nodes/formattedText/DashFieldView.scss | 4 +- .../nodes/formattedText/FormattedTextBox.scss | 20 +- .../views/nodes/formattedText/RichTextMenu.scss | 4 +- src/client/views/nodes/imageEditor/ImageEditor.tsx | 8 +- src/client/views/nodes/trails/PresBox.scss | 142 ++++----- src/client/views/search/SearchBox.scss | 10 +- src/client/views/topbar/TopBar.scss | 29 +- 93 files changed, 1909 insertions(+), 1931 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/packages/components/src/components/Button/Button.scss b/packages/components/src/components/Button/Button.scss index a31923e6d..bbe2e2470 100644 --- a/packages/components/src/components/Button/Button.scss +++ b/packages/components/src/components/Button/Button.scss @@ -1,118 +1,118 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .button-container { - position: relative; - width: fit-content; - padding: $padding; - cursor: pointer; - overflow: hidden; - user-select: none; - display: flex; - justify-content: center; - align-items: center; - gap: 5px; - font-family: $default-font; - border-radius: $standard-border-radius; - white-space: nowrap; - transition: 0.4s; - border: solid 1px; - border-color: transparent; - pointer-events: all; - - &.icon { - padding: 0; - gap: 0; - } - - .button-content { + position: relative; + width: fit-content; + padding: global.$padding; + cursor: pointer; + overflow: hidden; + user-select: none; display: flex; justify-content: center; align-items: center; - width: fit-content; - height: 100%; - z-index: 1; gap: 5px; + font-family: global.$default-font; + border-radius: global.$standard-border-radius; + white-space: nowrap; + transition: 0.4s; + border: solid 1px; + border-color: transparent; + pointer-events: all; - .icon { - display: flex; - justify-content: center; - align-items: center; + &.icon { + padding: 0; + gap: 0; } - } - - .background { - width: 100%; - height: 100%; - z-index: 0; - left: 0; - top: 0; - position: absolute; - transition: 0.4s; - } - &.inactive { - &:hover { - .background { - filter: opacity(0) !important; - } + .button-content { + display: flex; + justify-content: center; + align-items: center; + width: fit-content; + height: 100%; + z-index: 1; + gap: 5px; + + .icon { + display: flex; + justify-content: center; + align-items: center; + } } - } - &.primary { .background { - filter: opacity(0); - - &.active { - filter: opacity(0.2) !important; - } + width: 100%; + height: 100%; + z-index: 0; + left: 0; + top: 0; + position: absolute; + transition: 0.4s; } - &:hover{ - .background { - filter: opacity(0.2) - } + &.inactive { + &:hover { + .background { + filter: opacity(0) !important; + } + } } - } - &.secondary { - .background { - filter: opacity(0); + &.primary { + .background { + filter: opacity(0); - &.active { - filter: opacity(0.2) !important; - } - } + &.active { + filter: opacity(0.2) !important; + } + } - &:hover{ - .background { - filter: opacity(0.2) - } + &:hover { + .background { + filter: opacity(0.2); + } + } } - } - &.tertiary { - &:hover{ - box-shadow: $standard-shadow; - } + &.secondary { + .background { + filter: opacity(0); - .background { - filter: opacity(1) !important; + &.active { + filter: opacity(0.2) !important; + } + } + + &:hover { + .background { + filter: opacity(0.2); + } + } } - &:hover{ - .background { - filter: brightness(0.8); - } + &.tertiary { + &:hover { + box-shadow: global.$standard-shadow; + } + + .background { + filter: opacity(1) !important; + } + + &:hover { + .background { + filter: brightness(0.8); + } + } } - } - .label { - position: absolute; - bottom: 0; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - font-size: $xsmall-fontSize; - } + .label { + position: absolute; + bottom: 0; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: global.$xsmall-fontSize; + } } diff --git a/packages/components/src/components/ColorPicker/ColorPicker.scss b/packages/components/src/components/ColorPicker/ColorPicker.scss index e3ed32a45..32b912fe5 100644 --- a/packages/components/src/components/ColorPicker/ColorPicker.scss +++ b/packages/components/src/components/ColorPicker/ColorPicker.scss @@ -1,23 +1,23 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .colorPicker-container { - display: flex; - border-radius: $standard-border-radius; - width: fit-content; - height: fit-content; - position: relative; - - .colorPicker-toggle { - width: 100%; - height: 100%; - position: relative; - cursor: pointer; - } - - .colorPicker-popup { - position: absolute; - top: calc(100% + 5px); + display: flex; + border-radius: global.$standard-border-radius; width: fit-content; height: fit-content; - } + position: relative; + + .colorPicker-toggle { + width: 100%; + height: 100%; + position: relative; + cursor: pointer; + } + + .colorPicker-popup { + position: absolute; + top: calc(100% + 5px); + width: fit-content; + height: fit-content; + } } diff --git a/packages/components/src/components/Dropdown/Dropdown.scss b/packages/components/src/components/Dropdown/Dropdown.scss index 34ed84004..f9ea2711a 100644 --- a/packages/components/src/components/Dropdown/Dropdown.scss +++ b/packages/components/src/components/Dropdown/Dropdown.scss @@ -1,135 +1,136 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .dropdown { - margin-top: 10px; + margin-top: 10px; } .divider { - height: 1px; - width: 100%; - background: $medium-gray; + height: 1px; + width: 100%; + background: global.$medium-gray; } .dropdown-container { - display: flex; - flex-direction: column; - justify-content: center; - min-width: fit-content; - width: 100%; - border-radius: $standard-border-radius; - height: 100%; - position: relative; - transition: 0.4s; - - .dropdown-list { - position: absolute; - top: 100%; + display: flex; + flex-direction: column; + justify-content: center; + min-width: fit-content; width: 100%; - } - .dropdown-toggle-mini, - .dropdown-toggle { - width: calc(100% - 2px); - display: grid; - grid-template-columns: calc(100% - 30px) 30px; - grid-template-areas: 'button end'; - grid-template-rows: 1fr; + border-radius: global.$standard-border-radius; + height: 100%; position: relative; - align-items: center; - border: solid 1px; - border-color: transparent; - border-radius: $standard-border-radius; - overflow: hidden; - - &.inactive { - filter: opacity(0.5); - pointer-events: none; - cursor: not-allowed; - } + transition: 0.4s; - .background { - width: 100%; - height: 100%; - z-index: 0; - position: absolute; - transition: 0.4s; + .dropdown-list { + position: absolute; + top: 100%; + width: 100%; } + .dropdown-toggle-mini, + .dropdown-toggle { + width: calc(100% - 2px); + display: grid; + grid-template-columns: calc(100% - 30px) 30px; + grid-template-areas: 'button end'; + grid-template-rows: 1fr; + position: relative; + align-items: center; + border: solid 1px; + border-color: transparent; + border-radius: global.$standard-border-radius; + overflow: hidden; - &.inactive { - &:hover { - .background { - filter: opacity(0) !important; + &.inactive { + filter: opacity(0.5); + pointer-events: none; + cursor: not-allowed; } - } - } - - &.primary { - .background { - filter: opacity(0); - - &.active { - filter: opacity(0.2) !important; - } - } - - &:hover{ + .background { - filter: opacity(0.2) + width: 100%; + height: 100%; + z-index: 0; + position: absolute; + transition: 0.4s; } - } - } - - &.secondary { - .background { - filter: opacity(0); - - &.active { - filter: opacity(0.2) !important; + + &.inactive { + &:hover { + .background { + filter: opacity(0) !important; + } + } } - } - - &:hover{ - .background { - filter: opacity(0.2) + + &.primary { + .background { + filter: opacity(0); + + &.active { + filter: opacity(0.2) !important; + } + } + + &:hover { + .background { + filter: opacity(0.2); + } + } } - } - } - - &.tertiary { - &:hover{ - box-shadow: $standard-button-shadow; - } - - &:hover{ - .background { - filter: brightness(0.8); + + &.secondary { + .background { + filter: opacity(0); + + &.active { + filter: opacity(0.2) !important; + } + } + + &:hover { + .background { + filter: opacity(0.2); + } + } } - } - } - .toggle-button { - grid-area: button; - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; - min-width: 70px; - justify-self: center; - } + &.tertiary { + &:hover { + box-shadow: global.$standard-button-shadow; + } - .toggle-caret { - cursor: pointer; - grid-area: end; - display: flex; - justify-content: flex-end; - align-items: center; - justify-self: center; + &:hover { + .background { + filter: brightness(0.8); + } + } + } + + .toggle-button { + grid-area: button; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + min-width: 70px; + justify-self: center; + } + + .toggle-caret { + cursor: pointer; + grid-area: end; + display: flex; + justify-content: flex-end; + align-items: center; + justify-self: center; + } } - } - .dropdown-toggle-mini { - .toggle-caret { - position: absolute; - top:0; left:0; + .dropdown-toggle-mini { + .toggle-caret { + position: absolute; + top: 0; + left: 0; + } } - } } diff --git a/packages/components/src/components/DropdownSearch/DropdownSearch.scss b/packages/components/src/components/DropdownSearch/DropdownSearch.scss index e111c822b..d937df540 100644 --- a/packages/components/src/components/DropdownSearch/DropdownSearch.scss +++ b/packages/components/src/components/DropdownSearch/DropdownSearch.scss @@ -1,123 +1,122 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .dropdownsearch { - margin-top: 10px; + margin-top: 10px; } .divider { - height: 1px; - width: 100%; - background: $medium-gray; + height: 1px; + width: 100%; + background: global.$medium-gray; } .dropdownsearch-container { - display: flex; - flex-direction: column; - justify-content: center; - min-width: fit-content; - border-radius: $standard-border-radius; - height: 100%; - position: relative; - transition: 0.4s; - - .dropdownsearch-list { - position: absolute; - top: 100%; - width: 100%; - } - - .dropdownsearch-toggle { - width: 100%; - display: grid; - grid-template-columns: calc(100% - 30px) 30px; - grid-template-areas: 'button end'; - grid-template-rows: 1fr; + display: flex; + flex-direction: column; + justify-content: center; + min-width: fit-content; + border-radius: global.$standard-border-radius; + height: 100%; position: relative; - align-items: center; - border: solid 1px; - border-color: transparent; - border-radius: $standard-border-radius; - overflow: hidden; - - .toggle-background { - width: 100%; - height: 100%; - z-index: 0; - position: absolute; - transition: 0.4s; - - &.active { - filter: opacity(0.2) !important; - } + transition: 0.4s; + + .dropdownsearch-list { + position: absolute; + top: 100%; + width: 100%; } - &.primary { - color: $medium-blue; - .toggle-background { - background: $medium-blue; - filter: opacity(0); - } - - &:hover{ + .dropdownsearch-toggle { + width: 100%; + display: grid; + grid-template-columns: calc(100% - 30px) 30px; + grid-template-areas: 'button end'; + grid-template-rows: 1fr; + position: relative; + align-items: center; + border: solid 1px; + border-color: transparent; + border-radius: global.$standard-border-radius; + overflow: hidden; + .toggle-background { - filter: opacity(0.2) + width: 100%; + height: 100%; + z-index: 0; + position: absolute; + transition: 0.4s; + + &.active { + filter: opacity(0.2) !important; + } } - } - - } - - &.secondary { - .toggle-background { - background: $medium-blue; - filter: opacity(0); - } - - border: solid 1px $medium-blue; - color: $medium-blue; - - &:hover{ - .toggle-background { - filter: opacity(0.2) + + &.primary { + color: global.$medium-blue; + .toggle-background { + background: global.$medium-blue; + filter: opacity(0); + } + + &:hover { + .toggle-background { + filter: opacity(0.2); + } + } } - } - } - - &.tertiary { - color: white; - - .toggle-background { - background: $medium-blue; - } - - &:hover{ - box-shadow: $standard-button-shadow; - } - - &:hover{ - .toggle-background { - filter: brightness(0.8); + + &.secondary { + .toggle-background { + background: global.$medium-blue; + filter: opacity(0); + } + + border: solid 1px global.$medium-blue; + color: global.$medium-blue; + + &:hover { + .toggle-background { + filter: opacity(0.2); + } + } } - } - } - .toggle-button { - grid-area: button; - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; - min-width: 70px; - justify-self: center; - } + &.tertiary { + color: white; + + .toggle-background { + background: global.$medium-blue; + } - .toggle-caret { - cursor: pointer; - grid-area: end; - display: flex; - justify-content: flex-end; - align-items: center; - justify-self: center; + &:hover { + box-shadow: global.$standard-button-shadow; + } + + &:hover { + .toggle-background { + filter: brightness(0.8); + } + } + } + + .toggle-button { + grid-area: button; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + min-width: 70px; + justify-self: center; + } + + .toggle-caret { + cursor: pointer; + grid-area: end; + display: flex; + justify-content: flex-end; + align-items: center; + justify-self: center; + } } - } } diff --git a/packages/components/src/components/EditableText/EditableText.scss b/packages/components/src/components/EditableText/EditableText.scss index 19e5af2cd..15965e97e 100644 --- a/packages/components/src/components/EditableText/EditableText.scss +++ b/packages/components/src/components/EditableText/EditableText.scss @@ -1,131 +1,129 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .editableText-container { - position: relative; - width: fit-content; - border: solid 1px; - border-color: transparent; - border-radius: $standard-border-radius; - font-family: $default-font; - overflow: hidden; - padding: $padding; - - .password { - position: absolute; - display: flex; - justify-content: center; - align-items: center; - height: 100%; - right: 0; - top: 0; - } - - .editableText-background { - width: 100%; - height: 100%; - z-index: -1; - position: absolute; - transition: 0.4s; - top: 0; - left: 0; - } - - &.primary { - - &:focus-within { - .editableText-background { - filter: opacity(0.2) !important; - } + position: relative; + width: fit-content; + border: solid 1px; + border-color: transparent; + border-radius: global.$standard-border-radius; + font-family: global.$default-font; + overflow: hidden; + padding: global.$padding; + + .password { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + height: 100%; + right: 0; + top: 0; } .editableText-background { - filter: opacity(0); - - &.active { - filter: opacity(0.2) !important; - } + width: 100%; + height: 100%; + z-index: -1; + position: absolute; + transition: 0.4s; + top: 0; + left: 0; } - &:hover{ - .editableText-background { - filter: opacity(0.2) - } + &.primary { + &:focus-within { + .editableText-background { + filter: opacity(0.2) !important; + } + } + + .editableText-background { + filter: opacity(0); + + &.active { + filter: opacity(0.2) !important; + } + } + + &:hover { + .editableText-background { + filter: opacity(0.2); + } + } } - } - - &.secondary { - &:focus-within { - .editableText-background { - filter: opacity(0.2) !important; - } - } - - .editableText-background { - filter: opacity(0); - &.active { - filter: opacity(0.2) !important; - } + &.secondary { + &:focus-within { + .editableText-background { + filter: opacity(0.2) !important; + } + } + + .editableText-background { + filter: opacity(0); + + &.active { + filter: opacity(0.2) !important; + } + } + + &:hover { + .editableText-background { + filter: opacity(0.2); + } + } } - &:hover{ - .editableText-background { - filter: opacity(0.2) - } - } - } + &.tertiary { + &:hover { + box-shadow: global.$standard-shadow; + } - &.tertiary { - &:hover{ - box-shadow: $standard-shadow; + &:hover { + .editableText-background { + filter: brightness(0.8); + } + } } - &:hover{ - .editableText-background { - filter: brightness(0.8); - } + .editableText { + -webkit-appearance: none; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: inherit; + border: none; + outline: none; + margin: 0px !important; + padding: 0px !important; + box-shadow: none !important; + background: transparent; + color: inherit; + z-index: 1; + + &.center { + display: flex; + justify-content: center; + align-items: center; + } } - } - .editableText { - -webkit-appearance: none; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-size: inherit; - border: none; - outline: none; - margin: 0px !important; - padding: 0px !important; - box-shadow: none !important; - background: transparent; - color: inherit; - z-index: 1; - - &.center { - display: flex; - justify-content: center; - align-items: center; + .displayText { + cursor: text !important; + width: 100%; + height: 100%; + white-space: nowrap; + text-overflow: ellipsis; + display: flex; + align-items: center; + font-size: inherit; + color: inherit; + z-index: 1; + + &.center { + display: flex; + justify-content: center; + align-items: center; + } } - } - - .displayText { - cursor: text !important; - width: 100%; - height: 100%; - white-space: nowrap; - text-overflow: ellipsis; - display: flex; - align-items: center; - font-size: inherit; - color: inherit; - z-index: 1; - - &.center { - display: flex; - justify-content: center; - align-items: center; - } - } } - diff --git a/packages/components/src/components/FormInput/FormInput.scss b/packages/components/src/components/FormInput/FormInput.scss index db04ff8cf..2554cbd01 100644 --- a/packages/components/src/components/FormInput/FormInput.scss +++ b/packages/components/src/components/FormInput/FormInput.scss @@ -1,69 +1,69 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .formInput-container { - display: flex; - flex-direction: column; - width: 100%; - height: fit-content; - position: relative; - margin-top: 20px; - - .formInput { - font-family: inherit; + display: flex; + flex-direction: column; width: 100%; - border: 0; - border-bottom: 2px solid black; - outline: 0; - font-size: 1rem; - color: black; - padding: 7px 0; - background: transparent; - transition: border-color 0.2s; - - &::placeholder { - color: transparent; - } + height: fit-content; + position: relative; + margin-top: 20px; - &:focus { - ~ .formInput-label { - position: absolute; - transform: translate(0px, -13px); - display: block; - transition: 0.2s; + .formInput { + font-family: inherit; + width: 100%; + border: 0; + border-bottom: 2px solid black; + outline: 0; font-size: 1rem; - font-weight: 700; - } - padding-bottom: 6px; - font-weight: 700; - border-width: 3px; - border-image: linear-gradient(to right, black, white); - border-image-slice: 1; + color: black; + padding: 7px 0; + background: transparent; + transition: border-color 0.2s; + + &::placeholder { + color: transparent; + } + + &:focus { + ~ .formInput-label { + position: absolute; + transform: translate(0px, -13px); + display: block; + transition: 0.2s; + font-size: 1rem; + font-weight: 700; + } + padding-bottom: 6px; + font-weight: 700; + border-width: 3px; + border-image: linear-gradient(to right, black, white); + border-image-slice: 1; + } + + &:valid { + ~ .formInput-label { + position: absolute; + transform: translate(0px, -13px); + display: block; + transition: 0.2s; + font-size: 1rem; + } + } + + &:required, + &:invalid { + box-shadow: none; + } } - &:valid { - ~ .formInput-label { + .formInput-label { position: absolute; - transform: translate(0px, -13px); + top: 0; + transform: translate(0px, 8px); display: block; transition: 0.2s; font-size: 1rem; - } + color: gray; + pointer-events: none; } - - &:required, - &:invalid { - box-shadow: none; - } - } - - .formInput-label { - position: absolute; - top: 0; - transform: translate(0px, 8px); - display: block; - transition: 0.2s; - font-size: 1rem; - color: gray; - pointer-events: none; - } } diff --git a/packages/components/src/components/Group/Group.scss b/packages/components/src/components/Group/Group.scss index 885472a5d..7cd3dfd9e 100644 --- a/packages/components/src/components/Group/Group.scss +++ b/packages/components/src/components/Group/Group.scss @@ -1,16 +1,15 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .group-wrapper { - overflow: hidden; + overflow: hidden; - .group-container { - width: fit-content; - display: flex; - flex-flow: row wrap; - height: fit-content; - flex-flow: row; - justify-content: flex-start; - align-items: center; - } + .group-container { + width: fit-content; + display: flex; + flex-flow: row wrap; + height: fit-content; + flex-flow: row; + justify-content: flex-start; + align-items: center; + } } - diff --git a/packages/components/src/components/IconButton/IconButton.scss b/packages/components/src/components/IconButton/IconButton.scss index 9a0b53c0f..f899dc50f 100644 --- a/packages/components/src/components/IconButton/IconButton.scss +++ b/packages/components/src/components/IconButton/IconButton.scss @@ -1,121 +1,119 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .iconButton-container { - position: relative; - cursor: pointer; - overflow: hidden; - user-select: none; - display: flex; - justify-content: center; - align-items: center; - font-family: $default-font; - border-radius: $standard-border-radius; - white-space: nowrap; - transition: 0.4s; - border: solid 1px; - border-color: transparent; - pointer-events: all; - - - .iconButton-content { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - z-index: 1; - font-family: Verdana, sans-serif; - font-weight: 500; - } - - .icon { - z-index: 1; + position: relative; + cursor: pointer; + overflow: hidden; + user-select: none; display: flex; justify-content: center; align-items: center; - } - - .background { - width: 100%; - height: 100%; - z-index: 0; - position: absolute; + font-family: global.$default-font; + border-radius: global.$standard-border-radius; + white-space: nowrap; transition: 0.4s; - } + border: solid 1px; + border-color: transparent; + pointer-events: all; + + .iconButton-content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + z-index: 1; + font-family: Verdana, sans-serif; + font-weight: 500; + } - &.inactive { - .background { - filter: opacity(0) !important; + .icon { + z-index: 1; + display: flex; + justify-content: center; + align-items: center; } - } - &.primary { .background { - filter: opacity(0); - - &.active { - filter: opacity(0.2) !important; - } + width: 100%; + height: 100%; + z-index: 0; + position: absolute; + transition: 0.4s; } - &:hover{ - .background { - filter: opacity(0.2) - } + &.inactive { + .background { + filter: opacity(0) !important; + } } - } + &.primary { + .background { + filter: opacity(0); - &.secondary { - .background { - filter: opacity(0); + &.active { + filter: opacity(0.2) !important; + } + } - &.active { - filter: opacity(0.2) !important; - } + &:hover { + .background { + filter: opacity(0.2); + } + } } - &:hover{ - .background { - filter: opacity(0.2) - } - } - } + &.secondary { + .background { + filter: opacity(0); + + &.active { + filter: opacity(0.2) !important; + } + } - &.tertiary { - &:hover{ - box-shadow: $standard-button-shadow; + &:hover { + .background { + filter: opacity(0.2); + } + } } - &:hover{ - .background { - filter: brightness(0.8); - } + &.tertiary { + &:hover { + box-shadow: global.$standard-button-shadow; + } + + &:hover { + .background { + filter: brightness(0.8); + } + } } - } - .color { - position: relative; - width: 70%; - height: 15%; - z-index: 3; - margin-top: 2px; - border-radius: 10px; - outline: solid 0.3px; - outline-offset: -0.3px; - } + .color { + position: relative; + width: 70%; + height: 15%; + z-index: 3; + margin-top: 2px; + border-radius: 10px; + outline: solid 0.3px; + outline-offset: -0.3px; + } - .iconButton-label { - position: relative; - z-index: 2; - max-width: 100%; - overflow: hidden; - white-space: normal; - display: flex; - text-align: center; - justify-content: center; - align-items: center; - font-size: $xsmall-fontSize; - } + .iconButton-label { + position: relative; + z-index: 2; + max-width: 100%; + overflow: hidden; + white-space: normal; + display: flex; + text-align: center; + justify-content: center; + align-items: center; + font-size: global.$xsmall-fontSize; + } } diff --git a/packages/components/src/components/ListBox/ListBox.scss b/packages/components/src/components/ListBox/ListBox.scss index dc449c943..dc2a44513 100644 --- a/packages/components/src/components/ListBox/ListBox.scss +++ b/packages/components/src/components/ListBox/ListBox.scss @@ -1,4 +1,4 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .listBox-container { position: relative; @@ -13,4 +13,4 @@ max-width: 300px; padding: 5px; gap: 2px; -} \ No newline at end of file +} diff --git a/packages/components/src/components/ListItem/ListItem.scss b/packages/components/src/components/ListItem/ListItem.scss index 736078360..8d46605d0 100644 --- a/packages/components/src/components/ListItem/ListItem.scss +++ b/packages/components/src/components/ListItem/ListItem.scss @@ -1,78 +1,78 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .listItem-container { - position: relative; - width: 100%; - border-radius: $standard-border-radius; - display: flex; - justify-content: center; - align-items: flex-start; - flex-direction: column; - cursor: pointer; - font-family: Verdana, sans-serif; - overflow: hidden; - text-align: left; - - .listItem-background { - position: absolute; + position: relative; width: 100%; - height: 100%; - background: $medium-blue; - filter: opacity(0); - transition: 0.4s; - } - - .listItem-top { + border-radius: global.$standard-border-radius; display: flex; - height: 30px; - width: 100%; - justify-content: space-between; - align-items: center; - gap: 20px; + justify-content: center; + align-items: flex-start; + flex-direction: column; + cursor: pointer; + font-family: Verdana, sans-serif; + overflow: hidden; + text-align: left; - .content { - display: flex; - justify-content: flex-start; - align-items: center; - padding: $padding; - width: 100%; - height: 100%; - z-index: 1; - gap: 5px; - font-weight: 500; - - .text { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + .listItem-background { + position: absolute; width: 100%; - } + height: 100%; + background: global.$medium-blue; + filter: opacity(0); + transition: 0.4s; } - .shortcut { - grid-area: end; - padding: $padding; - display: flex; - justify-content: center; - align-items: center; - font-size: $xsmall-fontSize; - font-family: $default-font; - } + .listItem-top { + display: flex; + height: 30px; + width: 100%; + justify-content: space-between; + align-items: center; + gap: 20px; - .caret { - grid-area: end; - display: flex; - justify-content: flex-end; - align-items: center; - justify-self: center; + .content { + display: flex; + justify-content: flex-start; + align-items: center; + padding: global.$padding; + width: 100%; + height: 100%; + z-index: 1; + gap: 5px; + font-weight: 500; + + .text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } + } + + .shortcut { + grid-area: end; + padding: global.$padding; + display: flex; + justify-content: center; + align-items: center; + font-size: global.$xsmall-fontSize; + font-family: global.$default-font; + } + + .caret { + grid-area: end; + display: flex; + justify-content: flex-end; + align-items: center; + justify-self: center; + } } - } - .listItem-description { - font-size: $small-fontSize; - display: flex; - padding: 0px 5px 10px 5px; - justify-content: flex-start; - width: calc(100% - 10px); - } + .listItem-description { + font-size: global.$small-fontSize; + display: flex; + padding: 0px 5px 10px 5px; + justify-content: flex-start; + width: calc(100% - 10px); + } } diff --git a/packages/components/src/components/Modal/Modal.scss b/packages/components/src/components/Modal/Modal.scss index c0667ed26..a5698432d 100644 --- a/packages/components/src/components/Modal/Modal.scss +++ b/packages/components/src/components/Modal/Modal.scss @@ -1,46 +1,46 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .modal-container { - top: 0px; - left: 0px; - width: 100vw; - height: 100vh; - position: fixed; - display: flex; - justify-content: center; - align-items: center; - z-index: 100; - - .modal-popup { - position: relative; + top: 0px; + left: 0px; + width: 100vw; + height: 100vh; + position: fixed; display: flex; - flex-direction: column; - align-items: left; - z-index: 10; - width: 400px; - height: fit-content; - padding: 20px; - border-radius: $standard-border-radius; - font-weight: bold; - font-size: 1.5rem; + justify-content: center; + align-items: center; + z-index: 100; + + .modal-popup { + position: relative; + display: flex; + flex-direction: column; + align-items: left; + z-index: 10; + width: 400px; + height: fit-content; + padding: 20px; + border-radius: global.$standard-border-radius; + font-weight: bold; + font-size: 1.5rem; - .modal-closeButton { - top: -15px; - right: -15px; - position: absolute; - width: fit-content; - height: fit-content; + .modal-closeButton { + top: -15px; + right: -15px; + position: absolute; + width: fit-content; + height: fit-content; + } } - } } .modal-background { - z-index: 9; - position: absolute; - top: -10vh; - left: -10vw; - backdrop-filter: blur(15px); - width: 200vw; - height: 200vh; - background: $modal-background; + z-index: 9; + position: absolute; + top: -10vh; + left: -10vw; + backdrop-filter: blur(15px); + width: 200vw; + height: 200vh; + background: global.$modal-background; } diff --git a/packages/components/src/components/MultiToggle/MultiToggle.scss b/packages/components/src/components/MultiToggle/MultiToggle.scss index 2522549e9..854abdca0 100644 --- a/packages/components/src/components/MultiToggle/MultiToggle.scss +++ b/packages/components/src/components/MultiToggle/MultiToggle.scss @@ -1,5 +1 @@ -@import '../../global/globalCssVariables.scss'; - -.multiToggle-container { - -} \ No newline at end of file +@use '../../global/globalCssVariables.scss' as global; diff --git a/packages/components/src/components/NumberDropdown/NumberDropdown.scss b/packages/components/src/components/NumberDropdown/NumberDropdown.scss index 4ed5855d9..b3111623b 100644 --- a/packages/components/src/components/NumberDropdown/NumberDropdown.scss +++ b/packages/components/src/components/NumberDropdown/NumberDropdown.scss @@ -1,7 +1,4 @@ -@import '../../global/globalCssVariables.scss'; - -.numberDropdown-container { -} +@use '../../global/globalCssVariables.scss' as global; .form-wrapper { .iconButton-label { @@ -14,6 +11,6 @@ text-align: center; justify-content: center; align-items: center; - font-size: $xsmall-fontSize !important; + font-size: global.$xsmall-fontSize !important; } } diff --git a/packages/components/src/components/NumberInput/NumberInput.scss b/packages/components/src/components/NumberInput/NumberInput.scss index 2a562d395..887d64857 100644 --- a/packages/components/src/components/NumberInput/NumberInput.scss +++ b/packages/components/src/components/NumberInput/NumberInput.scss @@ -1,5 +1,5 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .numberInput-container { width: 100%; -} \ No newline at end of file +} diff --git a/packages/components/src/components/Popup/Popup.scss b/packages/components/src/components/Popup/Popup.scss index 39dd2c947..3087293f1 100644 --- a/packages/components/src/components/Popup/Popup.scss +++ b/packages/components/src/components/Popup/Popup.scss @@ -1,30 +1,30 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .popup-wrapper { - width: fit-content; - - &.fillWidth { - width: 100%; - } - - .trigger-container { width: fit-content; - height: fit-content; &.fillWidth { - width: 100%; + width: 100%; + } + + .trigger-container { + width: fit-content; + height: fit-content; + + &.fillWidth { + width: 100%; + } } - } } .popup-container { - display: flex; - height: fit-content; - min-width: fit-content; - width: fit-content; - position: relative; - border: solid 1px $black; - border-radius: $standard-border-radius; - overflow: hidden; - background: $white; + display: flex; + height: fit-content; + min-width: fit-content; + width: fit-content; + position: relative; + border: solid 1px global.$black; + border-radius: global.$standard-border-radius; + overflow: hidden; + background: global.$white; } diff --git a/packages/components/src/components/Slider/Slider.scss b/packages/components/src/components/Slider/Slider.scss index 9a9fc6172..a653f024f 100644 --- a/packages/components/src/components/Slider/Slider.scss +++ b/packages/components/src/components/Slider/Slider.scss @@ -1,168 +1,163 @@ -@import '../../global/globalCssVariables.scss'; +@use '../../global/globalCssVariables.scss' as global; .slider-container { - display: flex; - position: relative; - justify-content: center; - align-items: center; - min-width: 200px; - width: 100%; - height: 100%; - font-family: $default-font; - - .selected-range { - width: 100%; - background: $medium-blue; - } - - .range { - position: absolute; - background: $light-gray; - } - - .box-minmax{ - width: 100%; display: flex; - justify-content: space-between; - font-size: 20px; - color: $medium-blue; - position: absolute; - top: 110%; - } - .range-slider { - margin: 0px; - position: absolute; + position: relative; + justify-content: center; + align-items: center; + min-width: 200px; width: 100%; height: 100%; - top: 0px; - left: 0px; - - .rs-label-container { - display: flex; - position: absolute; - justify-content: center; - align-items: center; - overflow: visible; - border-radius: $standard-border-radius; - z-index: 45; - pointer-events: none; - - .rs-label { - display: flex; - font-size: smaller; - white-space: nowrap; - border-radius: 100%; - text-align: center; - text-wrap: wrap; - word-break: break-all; - justify-content: center; - align-items: center; - font-family: $default-font; - user-select: none; - pointer-events: none; - top: 0px; - width: fit-content; - border-radius: $standard-border-radius; - z-index: 40; - } - - } - - .rs-range { - width: 100%; - position: relative; - background: transparent; - pointer-events: none; - -webkit-appearance: none; - margin: 0px; - z-index: 20; - - &:focus { - outline: none; - } - - &::-webkit-slider-runnable-track { - width: 100%; - background: none; - cursor: pointer; - box-shadow: none; - -webkit-appearance: none; - pointer-events: none; - } - &::-moz-range-track { - width: 100%; - cursor: pointer; - box-shadow: none; - -webkit-appearance: none; - pointer-events: none; - } - - &::-webkit-slider-thumb { - cursor: ew-resize; - -webkit-appearance: none; - pointer-events: auto; - } - &::-moz-range-thumb { - cursor: pointer; - -webkit-appearance: none; - pointer-events: auto; - } - - &::-moz-focus-outer { - border: 0; - } - - &.xsmall { - &::-webkit-slider-runnable-track { - height: $xsmall; - } + font-family: global.$default-font; - &::-webkit-slider-thumb { - height: $xsmall; - width: $xsmall; - border-radius: $xsmall; - } + .selected-range { + width: 100%; + background: global.$medium-blue; } - &.small { - &::-webkit-slider-runnable-track { - height: $small; - } - - &::-webkit-slider-thumb { - height: $small; - width: $small; - border-radius: $small; - } + .range { + position: absolute; + background: global.$light-gray; } - &.medium { - &::-webkit-slider-runnable-track { - height: $medium; - } - - &::-webkit-slider-thumb { - height: $medium; - width: $medium; - border-radius: $medium; - } + .box-minmax { + width: 100%; + display: flex; + justify-content: space-between; + font-size: 20px; + color: global.$medium-blue; + position: absolute; + top: 110%; } - - &.large { - &::-webkit-slider-runnable-track { - height: $large; - } - - &::-webkit-slider-thumb { - height: $large; - width: $large; - border-radius: $large; - } + .range-slider { + margin: 0px; + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + + .rs-label-container { + display: flex; + position: absolute; + justify-content: center; + align-items: center; + overflow: visible; + border-radius: global.$standard-border-radius; + z-index: 45; + pointer-events: none; + + .rs-label { + display: flex; + font-size: smaller; + white-space: nowrap; + border-radius: 100%; + text-align: center; + text-wrap: wrap; + word-break: break-all; + justify-content: center; + align-items: center; + font-family: global.$default-font; + user-select: none; + pointer-events: none; + top: 0px; + width: fit-content; + border-radius: global.$standard-border-radius; + z-index: 40; + } + } + + .rs-range { + width: 100%; + position: relative; + background: transparent; + pointer-events: none; + -webkit-appearance: none; + margin: 0px; + z-index: 20; + + &:focus { + outline: none; + } + + &::-webkit-slider-runnable-track { + width: 100%; + background: none; + cursor: pointer; + box-shadow: none; + -webkit-appearance: none; + pointer-events: none; + } + &::-moz-range-track { + width: 100%; + cursor: pointer; + box-shadow: none; + -webkit-appearance: none; + pointer-events: none; + } + + &::-webkit-slider-thumb { + cursor: ew-resize; + -webkit-appearance: none; + pointer-events: auto; + } + &::-moz-range-thumb { + cursor: pointer; + -webkit-appearance: none; + pointer-events: auto; + } + + &::-moz-focus-outer { + border: 0; + } + + &.xsmall { + &::-webkit-slider-runnable-track { + height: global.$xsmall; + } + + &::-webkit-slider-thumb { + height: global.$xsmall; + width: global.$xsmall; + border-radius: global.$xsmall; + } + } + + &.small { + &::-webkit-slider-runnable-track { + height: global.$small; + } + + &::-webkit-slider-thumb { + height: global.$small; + width: global.$small; + border-radius: global.$small; + } + } + + &.medium { + &::-webkit-slider-runnable-track { + height: global.$medium; + } + + &::-webkit-slider-thumb { + height: global.$medium; + width: global.$medium; + border-radius: global.$medium; + } + } + + &.large { + &::-webkit-slider-runnable-track { + height: global.$large; + } + + &::-webkit-slider-thumb { + height: global.$large; + width: global.$large; + border-radius: global.$large; + } + } + } } - } - } - } - - - diff --git a/packages/components/src/components/Slider/Slider.tsx b/packages/components/src/components/Slider/Slider.tsx index 3ca51efed..f6f53799c 100644 --- a/packages/components/src/components/Slider/Slider.tsx +++ b/packages/components/src/components/Slider/Slider.tsx @@ -1,178 +1,188 @@ -import React, { useEffect, useRef, useState } from 'react' -import { Colors, getFontSize, getHeight, IGlobalProps, Size , getFormLabelSize, isDark, INumberProps } from '../../global' -import './Slider.scss' +import React, { useState } from 'react'; +import { Colors, getFontSize, getHeight, Size, getFormLabelSize, isDark, INumberProps } from '../../global'; +import './Slider.scss'; export interface ISliderProps extends INumberProps { - multithumb: boolean - autorangeMinVal?: number // minimimum value that min can have when autoranging - autorangeMinSize?: number // minimum difference between min and max when autoranging - autorange?: number // automatically adjust min/max to be +/- autorange/2 around the current value when the thumb is 15% from the min/max, or when the multithumbs are within 20% of the range and the range is bigger than autorange - endNumber?: number - setEndNumber?: (newVal: number) => void - setFinalNumber?: (newVal: number) => void - setFinalEndNumber?: (newVal: number) => void - decimals?: number; - step?: number - minDiff?: number + multithumb: boolean; + autorangeMinVal?: number; // minimimum value that min can have when autoranging + autorangeMinSize?: number; // minimum difference between min and max when autoranging + autorange?: number; // automatically adjust min/max to be +/- autorange/2 around the current value when the thumb is 15% from the min/max, or when the multithumbs are within 20% of the range and the range is bigger than autorange + endNumber?: number; + setEndNumber?: (newVal: number) => void; + setFinalNumber?: (newVal: number) => void; + setFinalEndNumber?: (newVal: number) => void; + decimals?: number; + step?: number; + minDiff?: number; } -let lastVal = 0; // bcz: WHY do I have to do this?? the pointerdown event locks in the value of 'valLoc' when it's created so need some other way to get the current value to that old handler... +let lastVal = 0; // bcz: WHY do I have to do this?? the pointerdown event locks in the value of 'valLoc' when it's created so need some other way to get the current value to that old handler... let lastEndVal = 0; export const Slider = (props: ISliderProps) => { - const [width, setWidth] = useState(100); - const [valLoc, setNumberLoc] = useState(props.number??(props.min + (props.max-props.min)/2)); - const [endNumberLoc, setEndNumberLoc] = useState(props.endNumber??(props.min + 2*(props.max-props.min)/3)); - const [min, setMin] = useState(props.min); - const [max, setMax] = useState(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(100); + const [valLoc, setNumberLoc] = useState(props.number ?? props.min + (props.max - props.min) / 2); + const [endNumberLoc, setEndNumberLoc] = useState(props.endNumber ?? props.min + (2 * (props.max - props.min)) / 3); + const [min, setMin] = useState(props.min); + const [max, setMax] = useState(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 (
- - {toDecimal(locVal)} - -
) - } - 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 ( +
+ {toDecimal(locVal)} +
+ ); + }; + 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 (
- {getValueLabel(val)} - document.addEventListener('pointerup', valPointerup, true)} - onChange={e => { - onchange(+e.target.value); - e.stopPropagation(); - }} - /> -
); - } - 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 ( +
+ {getValueLabel(val)} + document.addEventListener('pointerup', valPointerup, true)} + onChange={e => { + onchange(+e.target.value); + e.stopPropagation(); + }} + /> +
+ ); + }; + 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 = ( -
{ - lastVal = valLoc; - lastEndVal = endNumberLoc; - }} - style={{ - padding: `5px 0px ${getHeight(height, size)}px 0px`, - width: fillWidth ? '100%' : 'fit-content' - }}> -
{ - r && new ResizeObserver(() => setWidth(+(r?.clientWidth??100))).observe(r); - setWidth(+(r?.clientWidth??100)); - }} - style={{height: getHeight(height, size)}} - onPointerDown={onPointerDown} - > - {Slider} -
-
-
- {toDecimal(min)}{unit} - {toDecimal(max)}{unit} + const slider: JSX.Element = ( +
{ + lastVal = valLoc; + lastEndVal = endNumberLoc; + }} + style={{ + padding: `5px 0px ${getHeight(+(height || 0), size)}px 0px`, + width: fillWidth ? '100%' : 'fit-content', + }}> +
{ + r && new ResizeObserver(() => setWidth(+(r?.clientWidth ?? 100))).observe(r); + setWidth(+(r?.clientWidth ?? 100)); + }} + style={{ height: getHeight(+(height || 0), size) }} + onPointerDown={onPointerDown}> + {Slider} +
+
+
+ + {toDecimal(min)} + {unit} + + + {toDecimal(max)} + {unit} + +
+
-
-
- ) - - return ( - formLabel ? -
-
{formLabel}
- {slider} -
- : - slider -) -} + ); + return formLabel ? ( +
+
+ {formLabel} +
+ {slider} +
+ ) : ( + 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: '' 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: "" 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: "", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet) - { scripts: { }, opts: { title: "linker", layout: "", toolTip: "link started"}}, - { scripts: { }, opts: { title: "currently playing", layout: "", toolTip: "currently playing media"}}, - { scripts: { }, opts: { title: "Branching", layout: "", 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: "", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet) + { scripts: { }, opts: { title: "linker", layout: "", toolTip: "link started"}}, + { scripts: { }, opts: { title: "currently playing", layout: "", toolTip: "currently playing media"}}, + { scripts: { onClick: "hideUI()"},opts: { title: "Toggle UI", icon: "portrait", toolTip: "Toggle visibility of UI buttons"}}, + { scripts: { }, opts: { title: "Branching", layout: "", toolTip: "Branch, baby!"}} ]; const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List(['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 { 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, }}> 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 { @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 { 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 { + // 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<{}> {
{this._labelGroups.map(group => { return ( -
+

{group}

{ 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 height directly, but you can set the height of all of it's 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 height directly, but you can set the height of all of it's 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 {this._props.DocumentView?.() ? : null}
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 Date: Fri, 28 Feb 2025 15:40:51 -0500 Subject: fixed gptpopup disappearing in overlayView. added creation of images based on selection image in gptpopup. --- src/client/views/LightboxView.tsx | 137 +++++++++++++++-------------- src/client/views/MainView.tsx | 1 - src/client/views/OverlayView.tsx | 11 +++ src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 21 +++-- 5 files changed, 97 insertions(+), 75 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 0d3feb073..0be847281 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -265,75 +265,80 @@ export class LightboxView extends ObservableReactComponent { />
); - return !this._doc ? ( - - ) : ( -
{ - downx = e.clientX; - downy = e.clientY; - }} - onClick={e => ClientUtils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}> -
- - { - this._docView = r !== null ? r : undefined; - })} - Document={this._doc} - PanelWidth={this.lightboxWidth} - PanelHeight={this.lightboxHeight} - LayoutTemplate={this.lightboxDocTemplate} - isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected. - isContentActive={returnTrue} - styleProvider={DefaultStyleProvider} - ScreenToLocalTransform={this.lightboxScreenToLocal} - renderDepth={0} - suppressSetHeight={!!this._doc._layout_fitWidth} - containerViewPath={returnEmptyDocViewList} - childFilters={returnEmptyFilter} - childFiltersByRanges={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - addDocument={undefined} - removeDocument={undefined} - whenChildContentsActiveChanged={emptyFunction} - addDocTab={this.AddDocTab} - pinToPres={DocumentView.PinDoc} - focus={emptyFunction} - /> - + return ( + <> +
+
+ {!this._doc ? null : ( +
{ + downx = e.clientX; + downy = e.clientY; + }} + onClick={e => ClientUtils.isClick(e.clientX, e.clientY, downx, downy, Date.now()) && this.SetLightboxDoc(undefined)}> +
+ + { + this._docView = r !== null ? r : undefined; + })} + Document={this._doc} + PanelWidth={this.lightboxWidth} + PanelHeight={this.lightboxHeight} + LayoutTemplate={this.lightboxDocTemplate} + isDocumentActive={returnTrue} // without this being true, sidebar annotations need to be activated before text can be selected. + isContentActive={returnTrue} + styleProvider={DefaultStyleProvider} + ScreenToLocalTransform={this.lightboxScreenToLocal} + renderDepth={0} + suppressSetHeight={!!this._doc._layout_fitWidth} + containerViewPath={returnEmptyDocViewList} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + addDocument={undefined} + removeDocument={undefined} + whenChildContentsActiveChanged={emptyFunction} + addDocTab={this.AddDocTab} + pinToPres={DocumentView.PinDoc} + focus={emptyFunction} + /> + +
- {this._showPalette && (this._annoPaletteView = r)} Document={DocCast(Doc.UserDoc().myLightboxDrawings)} />} - {this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length ? true : false, this.previous)} - {this.renderNavBtn( - this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]), - undefined, - this._props.PanelHeight / 2 - 12.5, - 'chevron-right', - this._doc && this._future.length ? true : false, - this.next, - this.future().length.toString() + {this._showPalette && (this._annoPaletteView = r)} Document={DocCast(Doc.UserDoc().myLightboxDrawings)} />} + {this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length ? true : false, this.previous)} + {this.renderNavBtn( + this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]), + undefined, + this._props.PanelHeight / 2 - 12.5, + 'chevron-right', + this._doc && this._future.length ? true : false, + this.next, + this.future().length.toString() + )} + + {toggleBtn('lightboxView-navBtn', 'toggle reading view', BoolCast(this._doc?._layout_fitWidth), 'book-open', 'book', this.toggleFitWidth)} + {toggleBtn('lightboxView-tabBtn', 'open document in a tab', false, 'file-export', '', this.downloadDoc)} + {toggleBtn('lightboxView-paletteBtn', 'toggle sticker palette', this._showPalette === true, 'palette', '', this.togglePalette)} + {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Ink, 'pen', '', this.togglePen)} + {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', SnappingManager.ExploreMode, 'globe-americas', '', this.toggleExplore)} +
)} - - {toggleBtn('lightboxView-navBtn', 'toggle reading view', BoolCast(this._doc?._layout_fitWidth), 'book-open', 'book', this.toggleFitWidth)} - {toggleBtn('lightboxView-tabBtn', 'open document in a tab', false, 'file-export', '', this.downloadDoc)} - {toggleBtn('lightboxView-paletteBtn', 'toggle sticker palette', this._showPalette === true, 'palette', '', this.togglePalette)} - {toggleBtn('lightboxView-penBtn', 'toggle pen annotation', Doc.ActiveTool === InkTool.Ink, 'pen', '', this.togglePen)} - {toggleBtn('lightboxView-exploreBtn', 'toggle navigate only mode', SnappingManager.ExploreMode, 'globe-americas', '', this.toggleExplore)} -
+ ); } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8b0354471..631c10b89 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -169,7 +169,6 @@ export class MainView extends ObservableReactComponent { mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); componentDidMount() { - OverlayView.Instance.addWindow(, { x: 400, y: 200, width: 500, height: 400, title: 'GPT', backgroundColor: 'transparent', isHidden: () => !SnappingManager.ChatVisible, onClick: () => SnappingManager.SetChatVisible(false) }); // Utils.TraceConsoleLog(); reaction( // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 20931fc3d..6686a162e 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -19,6 +19,7 @@ import './OverlayView.scss'; import { DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; import { SnappingManager } from '../util/SnappingManager'; +import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; export type OverlayDisposer = () => void; @@ -126,6 +127,16 @@ export class OverlayView extends ObservableReactComponent { makeObservable(this); if (!OverlayView.Instance) { OverlayView.Instance = this; + this.addWindow(, { + x: 400, + y: 200, + width: 500, + height: 400, + title: 'GPT', // + backgroundColor: 'transparent', + isHidden: () => !SnappingManager.ChatVisible, + onClick: () => SnappingManager.SetChatVisible(false), + }); new ResizeObserver( action(entries => { Array.from(entries).forEach(entry => { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d122ca5b0..279317f49 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -36,7 +36,7 @@ import { OverlayView } from '../OverlayView'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { PinDocView, PinProps } from '../PinFuncs'; import { DrawingFillHandler } from '../smartdraw/DrawingFillHandler'; -import { FireflyImageData, FireflyImageDimensions, isFireflyImageData } from '../smartdraw/FireflyConstants'; +import { FireflyImageData, isFireflyImageData } from '../smartdraw/FireflyConstants'; import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import { StickerPalette } from '../smartdraw/StickerPalette'; import { StyleProp } from '../StyleProp'; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 96c7f37a3..79f5121ed 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -28,6 +28,8 @@ import './GPTPopup.scss'; import { FireflyImageDimensions } from '../../smartdraw/FireflyConstants'; import { Upload } from '../../../../server/SharedMediaTypes'; import { OpenWhere } from '../../nodes/OpenWhere'; +import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; +import { ImageField } from '../../../../fields/URLField'; export enum GPTPopupMode { SUMMARY, // summary of seleted document text @@ -204,13 +206,18 @@ export class GPTPopup extends ObservableReactComponent { )) // prettier-ignore generateFireflyImage = (imgDesc: string) => { - // if (this._fireflyRefStrength) { - // DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title), this.Document)?.then( - // action(() => { - // this._regenerateLoading = false; - // }) - // ); - // } else∂ + const selView = DocumentView.Selected().lastElement(); + const selDoc = selView?.Document; + if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutFieldKey(selDoc)] instanceof ImageField)) { + const oldPrompt = StrCast(selDoc.ai_firefly_prompt, StrCast(selDoc.title)); + const newPrompt = oldPrompt ? `${oldPrompt} ~~~ ${imgDesc}` : imgDesc; + return DrawingFillHandler.drawingToImage(selDoc, 100, newPrompt, selDoc) + .then(action(() => (this._userPrompt = ''))) + .catch(e => { + alert(e); + return undefined; + }); + } return SmartDrawHandler.CreateWithFirefly(imgDesc, FireflyImageDimensions.Square, 0) .then( action(doc => { -- cgit v1.2.3-70-g09d2 From dbe3a3cf1944a69a3ce492481bc22b8f1a8d53e8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 4 Mar 2025 18:21:47 -0500 Subject: added link cretor button to text anchor menu --- src/client/util/SharingManager.tsx | 6 ------ src/client/views/AntimodeMenu.scss | 1 - src/client/views/MainView.tsx | 2 -- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 14 +++++++++++++- src/client/views/pdf/AnchorMenu.tsx | 2 ++ 5 files changed, 15 insertions(+), 10 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index efc8e79a6..3a248400b 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -502,7 +502,6 @@ export class SharingManager extends React.Component { } }; - // eslint-disable-next-line react/sort-comp public close = action(() => { this.isOpen = false; this.selectedUsers = null; // resets the list of users and selected users (in the react-select component) @@ -517,7 +516,6 @@ export class SharingManager extends React.Component { this.layoutDocAcls = false; }); - // eslint-disable-next-line react/no-unused-class-component-methods public open = (target?: DocumentView, targetDoc?: Doc) => { this.populateUsers(); runInAction(() => { @@ -534,7 +532,6 @@ export class SharingManager extends React.Component { * @param group * @param emailId */ - // eslint-disable-next-line react/no-unused-class-component-methods shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => { const user = this.users.find(({ user: { email } }) => email === emailId)!; if (group.docsShared) { @@ -559,7 +556,6 @@ export class SharingManager extends React.Component { /** * Called from the properties sidebar to change permissions of a user. */ - // eslint-disable-next-line react/no-unused-class-component-methods shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => { if (layout) this.layoutDocAcls = true; if (shareWith !== 'Guest') { @@ -583,7 +579,6 @@ export class SharingManager extends React.Component { * @param group * @param emailId */ - // eslint-disable-next-line react/no-unused-class-component-methods removeMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; @@ -607,7 +602,6 @@ export class SharingManager extends React.Component { * Removes a group's permissions from documents that have been shared with it. * @param group */ - // eslint-disable-next-line react/no-unused-class-component-methods removeGroup = (group: Doc) => { if (group.docsShared) { DocListCast(group.docsShared).forEach(doc => { diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss index 48fa86276..c2f6ae62d 100644 --- a/src/client/views/AntimodeMenu.scss +++ b/src/client/views/AntimodeMenu.scss @@ -6,7 +6,6 @@ height: global.$antimodemenu-height; width: fit-content; border-radius: global.$standard-border-radius; - overflow: hidden; // box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); // border-radius: 0px 6px 6px 6px; display: flex; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 631c10b89..cc7c1a42b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -75,10 +75,8 @@ import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; import ImageEditorBox from './nodes/imageEditor/ImageEditor'; import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; -import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { SmartDrawHandler } from './smartdraw/SmartDrawHandler'; import { TopBar } from './topbar/TopBar'; -import { OverlayView } from './OverlayView'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e9c521aa0..c2a2caecf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1669,7 +1669,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent h) + .lastElement(), + '_blank' + ) + ?.focus(); + else FormattedTextBoxComment.update(this, this.EditorView!, undefined, dataset?.targethrefs, dataset?.linkdoc, dataset?.nopreview === 'true'); } }; @action diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 28371594e..9aa8fe649 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -18,6 +18,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { DrawingOptions, SmartDrawHandler } from '../smartdraw/SmartDrawHandler'; import './AnchorMenu.scss'; import { GPTPopup } from './GPTPopup/GPTPopup'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; @observer export class AnchorMenu extends AntimodeMenu { @@ -241,6 +242,7 @@ export class AnchorMenu extends AntimodeMenu { color={SettingsManager.userColor} /> )} + {this._selectedText && RichTextMenu.Instance?.createLinkButton()} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( Date: Tue, 4 Mar 2025 19:07:45 -0500 Subject: fixed maximizing mainview by fixing window event removal in dockingview. --- src/client/views/MainView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 30 ++++++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index cc7c1a42b..afefe3f03 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -686,7 +686,7 @@ export class MainView extends ObservableReactComponent { ); } @computed get mainDocView() { - const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView; + const headerBar = null; // this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView; return ( <> {headerBar} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 539b49c86..04e3b2663 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -48,8 +48,7 @@ export class CollectionDockingView extends CollectionSubView() { // eslint-disable-next-line no-use-before-define @observable public static Instance: CollectionDockingView | undefined = undefined; - private _reactionDisposer?: IReactionDisposer; - private _lightboxReactionDisposer?: IReactionDisposer; + private _disposers: { [key: string]: IReactionDisposer } = {}; private _containerRef = React.createRef(); private _flush: UndoManager.Batch | undefined; private _unmounting = false; @@ -66,6 +65,7 @@ export class CollectionDockingView extends CollectionSubView() { constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); + console.log('CREATING DOCKING VIEW'); if (this._props.renderDepth < 0) CollectionDockingView.Instance = this; // Why is this here? (window as unknown as { React: unknown }).React = React; @@ -282,6 +282,7 @@ export class CollectionDockingView extends CollectionSubView() { } setupGoldenLayout = async () => { if (this._unmounting) return; + console.log('SETUP LAYOUT'); // const config = StrCast(this.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.Document))); const config = StrCast(this.Document.dockingConfig); if (config) { @@ -339,29 +340,36 @@ export class CollectionDockingView extends CollectionSubView() { componentDidMount: () => void = async () => { this._props.setContentViewBox?.(this); this._unmounting = false; + console.log('MOUNTING'); SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property if (this._containerRef.current) { - this._lightboxReactionDisposer = reaction( + this._disposers.lightbox = reaction( () => DocumentView.LightboxDoc(), doc => setTimeout(() => !doc && this.onResize()) ); new ResizeObserver(this.onResize).observe(this._containerRef.current); - this._reactionDisposer = reaction( + this._disposers.docking = reaction( () => StrCast(this.Document.dockingConfig), config => { if (!this._goldenLayout || this._ignoreStateChange !== config) { + console.log('CONFIG CHANGED'); // bcz: TODO! really need to diff config with ignoreStateChange and modify the current goldenLayout instead of building a new one. this.setupGoldenLayout(); } this._ignoreStateChange = ''; } ); - reaction( + this._disposers.panel = reaction( () => this._props.PanelWidth(), - width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows + width => { + if (!this._goldenLayout && width > 20) { + console.log('PWIDTH = ' + width); + setTimeout(() => this.setupGoldenLayout()); + } + }, // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows { fireImmediately: true } ); - reaction( + this._disposers.color = reaction( () => [SnappingManager.userBackgroundColor, SnappingManager.userBackgroundColor], () => { clearStyleSheetRules(CollectionDockingView._highlightStyleSheet); @@ -375,7 +383,9 @@ export class CollectionDockingView extends CollectionSubView() { }; componentWillUnmount: () => void = () => { + console.log('UNMOUNTING'); this._unmounting = true; + Object.values(this._disposers).forEach(d => d()); try { this._goldenLayout.unbind('stackCreated', this.stackCreated); this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); @@ -385,9 +395,6 @@ export class CollectionDockingView extends CollectionSubView() { setTimeout(() => this._goldenLayout?.destroy()); window.removeEventListener('resize', this.onResize); window.removeEventListener('mouseup', this.onPointerUp); - - this._reactionDisposer?.(); - this._lightboxReactionDisposer?.(); }; // ViewBoxInterface overrides @@ -412,6 +419,7 @@ export class CollectionDockingView extends CollectionSubView() { .map(f => f as Doc); const changesMade = this.Document.dockingConfig !== json; if (changesMade) { + console.log('WRITING CONFIG'); if (![AclAdmin, AclEdit].includes(GetEffectiveAcl(this.dataDoc))) { this.layoutDoc.dockingConfig = json; this.layoutDoc.data = new List(docs); @@ -426,7 +434,7 @@ export class CollectionDockingView extends CollectionSubView() { @action onPointerUp = (): void => { - window.removeEventListener('pointerup', this.onPointerUp); + window.removeEventListener('mouseup', this.onPointerUp); DragManager.CompleteWindowDrag = undefined; setTimeout(this.endUndoBatch, 100); }; -- cgit v1.2.3-70-g09d2 From db3bdb19ff7bc1c69c544797c05a6db3b72b1464 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 5 Mar 2025 14:54:18 -0500 Subject: added:hover mode for images to make primary image fade in instead of alternate. --- src/client/documents/Documents.ts | 3 ++ src/client/views/MainView.tsx | 2 +- src/client/views/TagsView.tsx | 3 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/DocumentView.scss | 1 + src/client/views/nodes/DocumentView.tsx | 16 +++++---- src/client/views/nodes/ImageBox.scss | 6 ++++ src/client/views/nodes/ImageBox.tsx | 40 +++++++++++++++------- .../views/nodes/chatbot/tools/ImageCreationTool.ts | 3 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 3 ++ src/client/views/smartdraw/DrawingFillHandler.tsx | 4 +++ src/client/views/smartdraw/SmartDrawHandler.tsx | 8 +++-- 12 files changed, 65 insertions(+), 26 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1ce25165c..21d3c978b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -384,6 +384,9 @@ export class DocumentOptions { presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view', false); presentation_zoomText?: BOOLt = new BoolInfo('whether text anchors should shown in a larger box when following links to make them stand out', false); + data_annotations?: List; + _data_usePath?: STRt = new StrInfo("description of field key to display in image box ('alternate','alternate:hover', 'data:hover'). defaults to primary", false); + data_alternates?: List; data?: FieldType; data_useCors?: BOOLt = new BoolInfo('whether CORS protocol should be used for web page'); _face_showImages?: BOOLt = new BoolInfo('whether to show images in uniqe face Doc'); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index afefe3f03..cc7c1a42b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -686,7 +686,7 @@ export class MainView extends ObservableReactComponent { ); } @computed get mainDocView() { - const headerBar = null; // this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView; + const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView; return ( <> {headerBar} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index b70e21918..93d6fb684 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -398,8 +398,7 @@ export class TagsView extends ObservableReactComponent { e.stopPropagation(); }} type="text" - placeholder="Input tags for document..." - aria-label="tagsView-input" + placeholder="Enter #tags or @metadata" className="tagsView-input" style={{ width: '100%', borderRadius: '5px' }} /> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b3d908da4..43addfc29 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -344,7 +344,7 @@ export class CollectionFreeFormView extends CollectionSubView { const { pointFocus, zoomTime, didMove } = options; if (!this.Document.isGroup && pointFocus && !didMove) { - const dfltScale = this.isAnnotationOverlay ? 1 : 0.5; + const dfltScale = this.isAnnotationOverlay ? 1 : 0.25; if (this.layoutDoc[this.scaleFieldKey] !== dfltScale) { this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(pointFocus.X, pointFocus.Y), dfltScale, zoomTime); options.didMove = true; diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 294af4d96..dd5fd0d0c 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -291,6 +291,7 @@ justify-items: center; background-color: rgb(223, 223, 223); transform-origin: top left; + background: transparent; .documentView-editorView-resizer { height: 5px; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5f5dd1210..595abc7f8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -279,16 +279,17 @@ export class DocumentViewInternal extends DocComponent this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined }; onBrowseClick = (e: React.MouseEvent) => { - const browseTransitionTime = 500; + //const browseTransitionTime = 500; DocumentView.DeselectAll(); DocumentView.showDocument(this.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { - const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime }; + // const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime }; if (!focused && this._docView) { - this._docView - .docViewPath() - .reverse() - .forEach(cont => cont.ComponentView?.focus?.(cont.Document, options)); - Doc.linkFollowHighlight(this.Document, false); + DocumentView.showDocument(this.Document, { zoomScale: 0.3, willZoomCentered: true }); + // this._docView + // .docViewPath() + // .reverse() + // .forEach(cont => cont.ComponentView?.focus?.(cont.Document, options)); + // Doc.linkFollowHighlight(this.Document, false); } }); e.stopPropagation(); @@ -797,6 +798,7 @@ export class DocumentViewInternal extends DocComponent() { }; getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc._freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined); + @computed get usingAlternate() { + const usePath = StrCast(this.Document[this.fieldKey + '_usePath']); + return 'alternate' === usePath || ('alternate:hover' === usePath && this._isHovering) || (':hover' === usePath && !this._isHovering); + } + @computed get nativeSize() { TraceMobx(); if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 }; @@ -471,10 +476,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { alternate, - and show alternate on hover + and show + + primary on hover + }>
() { ref={this._overlayIconRef} onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => { - this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; + this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : usePath === 'alternate:hover' ? ':hover' : undefined; }) } style={{ @@ -527,7 +535,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { .filter(url => url) .map(url => this.choosePath(url)) ?? []; // acc ess the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; - return paths.length ? paths : [defaultUrl.href]; + return paths.length ? paths.reverse() : [defaultUrl.href]; } @computed get content() { @@ -552,7 +560,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { transformOrigin = 'right top'; transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`; } - const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`]; return (
() { ref={action((r: HTMLImageElement | null) => (this.imageRef = r))} key="paths" src={srcpath} - style={{ transform, transformOrigin, objectFit: 'fill', height: '100%' }} + style={{ transform, transformOrigin }} onError={action(e => { this._error = e.toString(); })} @@ -579,7 +586,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { width={nativeWidth} /> {fadepath === srcpath ? null : ( -
+
)} @@ -619,8 +626,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { return (
- Firefly: + + Firefly: + () {
- Similarity + + Similarity + () { return { width, height }; }; savedAnnotations = () => this._savedAnnotations; - render() { TraceMobx(); const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string; const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad; + const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); + const doc = this.usingAlternate ? (alts.lastElement() ?? this.Document) : this.Document; return (
() { { image_prompt, })) as { result: Upload.FileInformation & Upload.InspectionResults; url: string }; console.log('Image generation result:', result); - this._createImage(result, { text: RTFCast(image_prompt) }); + this._createImage(result, { text: RTFCast(image_prompt), ai: 'dall-e-3', tags: new List(['@ai']) }); return url ? [ { diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 79f5121ed..4dc45e6a0 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -30,6 +30,7 @@ import { Upload } from '../../../../server/SharedMediaTypes'; import { OpenWhere } from '../../nodes/OpenWhere'; import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; import { ImageField } from '../../../../fields/URLField'; +import { List } from '../../../../fields/List'; export enum GPTPopupMode { SUMMARY, // summary of seleted document text @@ -352,6 +353,8 @@ export class GPTPopup extends ObservableReactComponent { y: NumCast(textAnchor.y), _height: 200, _width: 200, + ai: 'dall-e', + tags: new List(['@ai']), data_nativeWidth: 1024, data_nativeHeight: 1024, }); diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index 0a30b14b8..c672bc718 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -1,6 +1,7 @@ import { imageUrlToBase64 } from '../../../ClientUtils'; import { Doc, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; +import { List } from '../../../fields/List'; import { DocCast, ImageCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { Upload } from '../../../server/SharedMediaTypes'; @@ -53,7 +54,10 @@ export class DrawingFillHandler { undefined, Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { ai: 'firefly', + tags: new List(['@ai']), title: newPrompt, + _data_usePath: 'alternate:hover', + data_alternates: new List([drawing]), ai_firefly_prompt: newPrompt, _width: 500, data_nativeWidth: info.nativeWidth, diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index ca308015d..1cceabed3 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -28,6 +28,7 @@ import { FireflyDimensionsMap, FireflyImageData, FireflyImageDimensions } from ' import './SmartDrawHandler.scss'; import { Upload } from '../../../server/SharedMediaTypes'; import { PointData } from '../../../pen-gestures/GestureTypes'; +import { List } from '../../../fields/List'; export interface DrawingOptions { text?: string; @@ -293,15 +294,17 @@ export class SmartDrawHandler extends ObservableReactComponent { return undefined; } const newseed = img.accessPaths.agnostic.client.match(/\/(\d+)upload/)?.[1]; - const imgDoc: Doc = Docs.Create.ImageDocument(img.accessPaths.agnostic.client, { + return Docs.Create.ImageDocument(img.accessPaths.agnostic.client, { title: input, nativeWidth: dims.width, nativeHeight: dims.height, + tags: new List(['@ai']), + _width: Math.min(400, dims.width), + _height: (Math.min(400, dims.width) * dims.height) / dims.width, ai: 'firefly', ai_firefly_seed: +(newseed ?? 0), ai_firefly_prompt: input, }); - return imgDoc; }) .catch(e => { alert('create image failed: ' + e.toString()); @@ -568,6 +571,7 @@ export class SmartDrawHandler extends ObservableReactComponent { color={SettingsManager.userColor} /> Date: Wed, 5 Mar 2025 19:36:07 -0500 Subject: fixed doccreatemenu warnings. --- src/client/views/MainView.tsx | 2 +- .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx | 350 +++++++++++---------- 2 files changed, 186 insertions(+), 166 deletions(-) (limited to 'src/client/views/MainView.tsx') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index fddc0e40c..ef8d0c197 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1140,7 +1140,7 @@ export class MainView extends ObservableReactComponent { - + diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx index 16d588c55..64416c26d 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Colors } from 'browndash-components'; +import { Colors } from '@dash/components'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { IDisposer } from 'mobx-utils'; @@ -20,7 +20,6 @@ import { UndoManager, undoable } from '../../../../util/UndoManager'; import { ObservableReactComponent } from '../../../ObservableReactComponent'; import { CollectionFreeFormView } from '../../../collections/collectionFreeForm/CollectionFreeFormView'; import { DocumentView, DocumentViewInternal } from '../../DocumentView'; -import { FieldViewProps } from '../../FieldView'; import { OpenWhere } from '../../OpenWhere'; import { DataVizBox } from '../DataVizBox'; import './DocCreatorMenu.scss'; @@ -64,22 +63,27 @@ export type Col = { defaultContent?: string; }; +interface DocCreateMenuProps { + addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; +} + @observer -export class DocCreatorMenu extends ObservableReactComponent { +export class DocCreatorMenu extends ObservableReactComponent { + // eslint-disable-next-line no-use-before-define static Instance: DocCreatorMenu; private _disposers: { [name: string]: IDisposer } = {}; private _ref: HTMLDivElement | null = null; - private templateManager: TemplateManager; + private templateManager: TemplateManager; @observable _fullyRenderedDocs: Doc[] = []; @observable _renderedDocCollectionPreview: Doc | undefined = undefined; @observable _renderedDocCollection: Doc | undefined = undefined; @observable _docsRendering: boolean = false; - @observable _userTemplates: {template: Template, doc: Doc}[] = []; //!!! used to keep track of all templates, should be refactored to work with actual templates and not docs + @observable _userTemplates: { template: Template; doc: Doc }[] = []; //!!! used to keep track of all templates, should be refactored to work with actual templates and not docs @observable _selectedTemplate: Template | undefined = undefined; @observable _currEditingTemplate: Template | undefined = undefined; @@ -92,7 +96,7 @@ export class DocCreatorMenu extends ObservableReactComponent { @observable _expandedPreview: Doc | undefined = undefined; @observable _suggestedTemplates: Template[] = []; - @observable _suggestedTemplatePreviews: {doc: Doc, template: Template}[] = []; + @observable _suggestedTemplatePreviews: { doc: Doc; template: Template }[] = []; @observable _GPTOpt: boolean = false; @observable _callCount: number = 0; @observable _GPTLoading: boolean = false; @@ -111,7 +115,7 @@ export class DocCreatorMenu extends ObservableReactComponent { @observable _draggingIndicator: boolean = false; @observable _dataViz?: DataVizBox; @observable _interactionLock: boolean | undefined; - @observable _snapPt: {x: number, y: number} = {x: 0, y: 0}; + @observable _snapPt: { x: number; y: number } = { x: 0, y: 0 }; @observable _resizeHdlId: string = ''; @observable _resizing: boolean = false; @observable _offset: { x: number; y: number } = { x: 0, y: 0 }; @@ -120,7 +124,7 @@ export class DocCreatorMenu extends ObservableReactComponent { @observable _menuDimensions: { width: number; height: number } = { width: 400, height: 400 }; @observable _editing: boolean = false; - constructor(props: any) { + constructor(props: DocCreateMenuProps) { super(props); makeObservable(this); DocCreatorMenu.Instance = this; @@ -138,14 +142,14 @@ export class DocCreatorMenu extends ObservableReactComponent { this._userCreatedFields = []; }; @action addUserTemplate = (template: Template) => { - this._userTemplates.push({template: template.cloneBase(), doc: template.getRenderedDoc()}); + this._userTemplates.push({ template: template.cloneBase(), doc: template.getRenderedDoc() }); }; @action removeUserTemplate = (template: Template) => { this._userTemplates = this._userTemplates.filter(info => info.template !== template); - } + }; @action updateTemplatePreview = (template: Template) => { template.renderUpdates(); - const preview = {template: template, doc: template.getRenderedDoc()}; + const preview = { template: template, doc: template.getRenderedDoc() }; this._suggestedTemplatePreviews = this._suggestedTemplatePreviews.map(t => { return t.template === preview.template ? preview : t }); //prettier-ignore this._userTemplates = this._userTemplates.map(t => { return t.template === preview.template ? preview : t }); //prettier-ignore }; @@ -216,7 +220,7 @@ export class DocCreatorMenu extends ObservableReactComponent { return bounds; } - setUpButtonClick = (e: any, func: () => void) => { + setUpButtonClick = (e: React.PointerEvent, func: () => void) => { setupMoveUpEvents( this, e, @@ -298,7 +302,7 @@ export class DocCreatorMenu extends ObservableReactComponent { SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them e.stopPropagation(); const id = (this._resizeHdlId = e.currentTarget.className); - const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as any).width.replace('px', '')) / 2 : 0; + const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as HTMLElement).width.replace('px', '')) / 2 : 0; const bounds = e.currentTarget.getBoundingClientRect(); this._offset = { x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, // @@ -309,7 +313,7 @@ export class DocCreatorMenu extends ObservableReactComponent { }; @action - onResize = (e: any): boolean => { + onResize = (e: PointerEvent): boolean => { const dragHdl = this._resizeHdlId.split(' ')[1]; const thisPt = DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y); @@ -324,7 +328,7 @@ export class DocCreatorMenu extends ObservableReactComponent { }; @action - onDrag = (e: any): boolean => { + onDrag = (e: PointerEvent): boolean => { this._pageX = e.pageX - (this._startPos?.x ?? 0); this._pageY = e.pageY - (this._startPos?.y ?? 0); this._initDimensions.x = this._pageX; @@ -377,7 +381,7 @@ export class DocCreatorMenu extends ObservableReactComponent { } else { this._selectedTemplate = template; template.renderUpdates(); - this._fullyRenderedDocs = await this.createDocsFromTemplate(template) ?? []; + this._fullyRenderedDocs = (await this.createDocsFromTemplate(template)) ?? []; this.updateRenderedDocCollection(); } }; @@ -486,9 +490,8 @@ export class DocCreatorMenu extends ObservableReactComponent { * @returns a doc containing the fully rendered template */ applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise