aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/ImageBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/ImageBox.tsx')
-rw-r--r--src/client/views/nodes/ImageBox.tsx232
1 files changed, 232 insertions, 0 deletions
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e4b3a1b9b..ff938df78 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -35,6 +35,15 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
+import { URLField } from '../../../fields/URLField';
+import { gptImageLabel } from '../../apis/gpt/GPT';
+import ReactLoading from 'react-loading';
+import { FollowLinkScript } from '../../documents/DocUtils';
+import { basename } from 'path';
+import { ImageUtility } from './generativeFill/generativeFillUtils/ImageHandler';
+import { dropActionType } from '../../util/DropActionTypes';
+import { canvasSize } from './generativeFill/generativeFillUtils/generativeFillConstants';
+import axios from 'axios';
export class ImageEditorData {
// eslint-disable-next-line no-use-before-define
@@ -59,6 +68,8 @@ export class ImageEditorData {
public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore
public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore
}
+
+const API_URL = 'https://api.unsplash.com/search/photos';
@observer
export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
@@ -73,9 +84,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
private _marqueeref = React.createRef<MarqueeAnnotator>();
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ private _imageRef: HTMLImageElement | null = null; // <video> ref
+ @observable private _width: number = 0;
+ @observable private _height: number = 0;
+ @observable private searchInput = '';
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@observable _curSuffix = '';
@observable _error = '';
+ @observable private _loading = false;
@observable _isHovering = false; // flag to switch between primary and alternate images on hover
_ffref = React.createRef<CollectionFreeFormView>();
@@ -148,6 +164,32 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
Object.values(this._disposers).forEach(disposer => disposer?.());
}
+ fetchImages = async () => {
+ try {
+ const { data } = await axios.get(`${API_URL}?query=${this.searchInput}&page=1&per_page=${1}&client_id=${process.env.VITE_API_KEY}`);
+ console.log('data', data);
+ console.log(data.results);
+ const imageSnapshot = Docs.Create.ImageDocument(data.results[0].urls.small, {
+ _nativeWidth: Doc.NativeWidth(this.layoutDoc),
+ _nativeHeight: Doc.NativeHeight(this.layoutDoc),
+ x: NumCast(this.layoutDoc.x),
+ y: NumCast(this.layoutDoc.y),
+ onClick: FollowLinkScript(),
+ _width: 150,
+ _height: 150,
+ title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
+ });
+ this._props.addDocument?.(imageSnapshot);
+ } catch (error) {
+ console.log(error);
+ }
+ };
+
+ handleSelection = async (selection: string) => {
+ this.searchInput = selection;
+ const images = await this.fetchImages();
+ };
+
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
@@ -259,10 +301,189 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return cropping;
};
+ createCanvas = async (downX?: number, downY?: number, cb?: (filename: string, x: number | undefined, y: number | undefined) => void) => {
+ const width = NumCast(this.layoutDoc._width);
+ const canvas = document.createElement('canvas');
+ // canvas.width = 640;
+ // canvas.height = (640 * Doc.NativeHeight(this.layoutDoc)) / (Doc.NativeWidth(this.layoutDoc) || 1);
+ canvas.width = NumCast(this.layoutDoc._width);
+ canvas.height = NumCast(this.layoutDoc._height);
+ const ctx = canvas.getContext('2d'); // draw image to canvas. scale to target dimensions
+ if (ctx) {
+ // this._imageRef && ctx.drawImage(this._imageRef, 0, 0, canvas.width, canvas.height);
+ this._imageRef && ctx.drawImage(this._imageRef, NumCast(this._marqueeref.current?.left), NumCast(this._marqueeref.current?.top), this._width, this._height, 0, 0, 1000, 1000);
+ //this._imageRef && ctx.drawImage(this._imageRef, 0, 0, 2000, 1000, 0, 0, canvas.width, canvas.height);
+ // console.log(NumCast(this._marqueeref.current?.left) + 100);
+ }
+ const blob = await ImageUtility.canvasToBlob(canvas);
+ return ImageBox.selectUrlToBase64(blob);
+
+ // if (this._imageRef) {
+ // const canv = ImageUtility.getCroppedImg(this._imageRef, this._imageRef.width, this._imageRef.height);
+ // console.log(this._imageRef.width);
+ // if (canv) {
+ // const blob = await ImageUtility.canvasToBlob(canv);
+ // return ImageBox.selectUrlToBase64(blob);
+ // }
+ // }
+ if (!this._imageRef) {
+ const b = Docs.Create.LabelDocument({
+ x: NumCast(this.layoutDoc.x) + width,
+ y: NumCast(this.layoutDoc.y, 1),
+ _width: 150,
+ _height: 50,
+ // title: (this.layoutDoc._layout_currentTimecode || 0).toString(),
+ onClick: FollowLinkScript(),
+ });
+ this._props.addDocument?.(b);
+ DocUtils.MakeLink(b, this.Document, { link_relationship: 'image snapshot' });
+ } else {
+ // convert to desired file format
+ // const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+ // // if you want to preview the captured image,
+ // const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
+ // const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_'));
+ // const filename = basename(encodedFilename);
+ // ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
+ }
+ // convert to desired file format
+
+ // const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+ // // if you want to preview the captured image,
+ // const retitled = StrCast(this.Document.title).replace(/[ -.:]/g, '');
+ // const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[./?=]/g, '_'));
+ // const filename = basename(encodedFilename);
+ //ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY));
+ // }
+ // const docViewContent = this.DocumentView?.().ContentDiv!;
+ // if (docViewContent instanceof HTMLCanvasElement) {
+ // const canvas = docViewContent;
+ // const img = document.createElement('img'); // create a Image Element
+ // img.src = canvas.toDataURL(); // image sourcez
+ // img.style.width = canvas.style.width;
+ // img.style.height = canvas.style.height;
+ // const parEle = newCan.parentElement as HTMLElement;
+ // parEle.removeChild(newCan);
+ // parEle.appendChild(img);
+ // }
+ };
+
+ createSnapshotLink = (imagePath: string, downX?: number, downY?: number) => {
+ const url = !imagePath.startsWith('/') ? ClientUtils.CorsProxy(imagePath) : imagePath;
+ const width = NumCast(this.layoutDoc._width) || 1;
+ const height = NumCast(this.layoutDoc._height);
+ const imageSnapshot = Docs.Create.ImageDocument(url, {
+ _nativeWidth: Doc.NativeWidth(this.layoutDoc),
+ _nativeHeight: Doc.NativeHeight(this.layoutDoc),
+ x: NumCast(this.layoutDoc.x) + width,
+ y: NumCast(this.layoutDoc.y),
+ onClick: FollowLinkScript(),
+ _width: 150,
+ _height: (height / width) * 150,
+ title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
+ });
+ Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc));
+ Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
+ this._props.addDocument?.(imageSnapshot);
+ DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
+ // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1
+ setTimeout(() => downX !== undefined && downY !== undefined && DocumentView.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
+ };
+
+ /**
+ *
+ if (oldDiv instanceof HTMLCanvasElement) {
+ const canvas = oldDiv;
+ const img = document.createElement('img'); // create a Image Element
+ img.src = canvas.toDataURL(); // image sourcez
+ img.style.width = canvas.style.width;
+ img.style.height = canvas.style.height;
+ const newCan = newDiv as HTMLCanvasElement;
+ const parEle = newCan.parentElement as HTMLElement;
+ parEle.removeChild(newCan);
+ parEle.appendChild(img);
+ }
+ */
+
+ static selectUrlToBase64 = async (blob: Blob): Promise<string> => {
+ try {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = () => resolve(reader.result as string);
+ reader.onerror = error => reject(error);
+ });
+ } catch (error) {
+ console.error('Error:', error);
+ throw error;
+ }
+ };
+
+ static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
+ try {
+ const response = await fetch(imageUrl);
+ const blob = await response.blob();
+
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = () => resolve(reader.result as string);
+ reader.onerror = error => reject(error);
+ });
+ } catch (error) {
+ console.error('Error:', error);
+ throw error;
+ }
+ };
+
+ getImageDesc = async () => {
+ // if (StrCast(this.dataDoc.description)) return StrCast(this.dataDoc.description); // Return existing description
+ const { href } = (this.dataDoc.data as URLField).url;
+ const hrefParts = href.split('.');
+ const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
+ this._loading = true;
+ try {
+ // const hrefBase64 = await ImageBox.imageUrlToBase64(hrefComplete);
+ const hrefBase64 = await this.createCanvas();
+ const response = await gptImageLabel(hrefBase64, 'Tell me what words you see on this image.');
+ //const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this text with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: ');
+ console.log(response);
+ // AnchorMenu.Instance.transferToFlashcard(response);
+ // this.Document[DocData].description = response.trim();
+ // return response; // Return the response
+ } catch (error) {
+ console.log('Error');
+ }
+ this._loading = false;
+ // return '';
+ };
+
+ @action
+ setRef = (iref: HTMLImageElement | null) => {
+ this._imageRef = iref;
+ // if (iref) {
+ // this._videoRef!.ontimeupdate = this.updateTimecode;
+ // // @ts-ignore
+ // // vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
+ // this._disposers.reactionDisposer?.();
+ // this._disposers.reactionDisposer = reaction(
+ // () => NumCast(this.layoutDoc._layout_currentTimecode),
+ // time => {
+ // !this._playing && (vref.currentTime = time);
+ // },
+ // { fireImmediately: true }
+ // );
+
+ // (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoThumbnails.DENSE) && this.getVideoThumbnails();
+ // }
+ };
+
specificContextMenu = (): void => {
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
const funcs: ContextMenuProps[] = [];
+ // funcs.push({ description: 'Create ai flashcards', event: () => this.getImageDesc(), icon: 'id-card' });
+ funcs.push({ description: 'Get Images', event: () => this.handleSelection('Cats'), icon: 'redo-alt' });
funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
@@ -391,6 +612,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
<div className="imageBox-fader" style={{ opacity: backAlpha }}>
<img
alt=""
+ ref={this.setRef}
key="paths"
src={srcpath}
style={{ transform, transformOrigin }}
@@ -432,6 +654,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
action(moveEv => {
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
+ this._width = moveEv.clientX;
+ this._height = moveEv.clientY;
return true;
}),
returnFalse,
@@ -443,6 +667,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
finishMarquee = () => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor;
+ AnchorMenu.Instance.gptFlashcards = this.getImageDesc;
+ AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
this._marqueeref.current?.onTerminateSelection();
this._props.select(false);
};
@@ -500,6 +726,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
addDocument={this.addDocument}>
{this.content}
</CollectionFreeFormView>
+ {this._loading ? (
+ <div className="loading-spinner" style={{ position: 'absolute' }}>
+ <ReactLoading type="spin" height={50} width={50} color={'blue'} />
+ </div>
+ ) : null}
{this.annotationLayer}
{!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
@@ -518,6 +749,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
marqueeContainer={this._mainCont.current}
highlightDragSrcColor=""
anchorMenuCrop={this.crop}
+ // anchorMenuFlashcard={() => this.getImageDesc()}
/>
)}
</div>