aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
authoralyssaf16 <alyssa_feinberg@brown.edu>2024-10-06 15:15:06 -0400
committeralyssaf16 <alyssa_feinberg@brown.edu>2024-10-06 15:15:06 -0400
commitfbff73033b6c0f9b1214e9013c155ff085e7a737 (patch)
treee5a278de98cb71fa023af7b85e20aafaf5ab03bf /src/client/views/nodes
parentfa54fdf2bf9bf8523da393a81ec1ac1cd4fa0f4c (diff)
parente61b2b356e761dfefda9d09bbbfc3c4a8d943f2c (diff)
Merge branch 'alyssa-starter' of https://github.com/brown-dash/Dash-Web into alyssa-starter
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/.FaceRectangles.tsx.icloudbin168 -> 0 bytes
-rw-r--r--src/client/views/nodes/.LinkAnchorBox.tsx.icloudbin167 -> 0 bytes
-rw-r--r--src/client/views/nodes/AudioBox.tsx3
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx190
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx1
-rw-r--r--src/client/views/nodes/DiagramBox.scss4
-rw-r--r--src/client/views/nodes/DiagramBox.tsx19
-rw-r--r--src/client/views/nodes/DocumentView.tsx21
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx8
-rw-r--r--src/client/views/nodes/ImageBox.tsx26
-rw-r--r--src/client/views/nodes/LabelBigText.js270
-rw-r--r--src/client/views/nodes/LabelBox.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.tsx47
-rw-r--r--src/client/views/nodes/VideoBox.tsx15
-rw-r--r--src/client/views/nodes/WebBox.tsx30
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx40
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx11
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFill.tsx104
-rw-r--r--src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx34
-rw-r--r--src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts16
20 files changed, 332 insertions, 509 deletions
diff --git a/src/client/views/nodes/.FaceRectangles.tsx.icloud b/src/client/views/nodes/.FaceRectangles.tsx.icloud
deleted file mode 100644
index 14c960022..000000000
--- a/src/client/views/nodes/.FaceRectangles.tsx.icloud
+++ /dev/null
Binary files differ
diff --git a/src/client/views/nodes/.LinkAnchorBox.tsx.icloud b/src/client/views/nodes/.LinkAnchorBox.tsx.icloud
deleted file mode 100644
index 2b5650f75..000000000
--- a/src/client/views/nodes/.LinkAnchorBox.tsx.icloud
+++ /dev/null
Binary files differ
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 6a918664c..2c0a102f5 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -8,7 +8,7 @@ import { DateField } from '../../../fields/DateField';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, DateCast, DocCast, NumCast } from '../../../fields/Types';
+import { Cast, DateCast, NumCast } from '../../../fields/Types';
import { AudioField, nullAudio } from '../../../fields/URLField';
import { formatTime } from '../../../Utils';
import { Docs } from '../../documents/Documents';
@@ -738,7 +738,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
ref={action((r: CollectionStackedTimeline | null) => {
this._stackedTimeline = r;
})}
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
dataFieldKey={this.fieldKey}
fieldKey={this.annotationKey}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 461a65c27..9fb8bc4d6 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,41 +1,34 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { MathJax, MathJaxContext } from 'better-react-mathjax';
import { Tooltip } from '@mui/material';
-import { action, computed, makeObservable, observable, reaction } from 'mobx';
+import axios from 'axios';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import ReactLoading from 'react-loading';
import { returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents } from '../../../ClientUtils';
import { emptyFunction } from '../../../Utils';
import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
import { RichTextField } from '../../../fields/RichTextField';
import { DocCast, NumCast, RTFCast, StrCast, toList } from '../../../fields/Types';
+import { nullAudio } from '../../../fields/URLField';
import { GPTCallType, gptAPICall, gptImageLabel } from '../../apis/gpt/GPT';
-import '../pdf/GPTPopup/GPTPopup.scss';
-import { DocUtils } from '../../documents/DocUtils';
+import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { dropActionType } from '../../util/DropActionTypes';
import { undoable } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { PinDocView, PinProps } from '../PinFuncs';
import { StyleProp } from '../StyleProp';
+import '../pdf/GPTPopup/GPTPopup.scss';
import './ComparisonBox.scss';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
-import ReactLoading from 'react-loading';
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { tickStep } from 'd3';
-import { CollectionCarouselView } from '../collections/CollectionCarouselView';
-import { FollowLinkScript } from '../../documents/DocUtils';
-import { schema } from '../nodes/formattedText/schema_rts';
-import { Id } from '../../../fields/FieldSymbols';
-import axios from 'axios';
-import ReactMarkdown from 'react-markdown';
-import { WebField, nullAudio } from '../../../fields/URLField';
const API_URL = 'https://api.unsplash.com/search/photos';
@@ -44,7 +37,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ComparisonBox, fieldKey);
}
+ private SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
+ private _closeRef = React.createRef<HTMLDivElement>();
private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
+ private _reactDisposer: IReactionDisposer | undefined;
constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
@@ -55,38 +51,19 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@observable private _outputValue = '';
@observable private _loading = false;
@observable private _isEmpty = false;
- @observable private _audio: Doc = Docs.Create.TextDocument('');
- @observable childActive = false;
- @observable _yRelativeToTop: boolean = true;
- @observable _animating = '';
- @observable mathJaxConfig = {
- loader: { load: ['input/asciimath'] },
- };
+ @observable private _childActive = false;
+ @observable private _animating = '';
@observable private _listening = false;
- @observable transcriptElement = '';
- @observable private frontSide = false;
- SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
+ @observable private _frontSide = false;
@observable recognition = new this.SpeechRecognition();
- _closeRef = React.createRef<HTMLDivElement>();
- get revealOp() {
- return this.layoutDoc[`_${this._props.fieldKey}_revealOp`];
- }
- get clipHeightKey() {
- return '_' + this._props.fieldKey + '_clipHeight';
- }
-
- get clipWidthKey() {
- return '_' + this._props.fieldKey + '_clipWidth';
- }
-
- @computed get clipWidth() {
- return NumCast(this.layoutDoc[this.clipWidthKey], 50);
- }
-
- @computed get clipHeight() {
- return NumCast(this.layoutDoc[this.clipHeightKey], 200);
- }
+ @computed get revealOpKey() { return `_${this._props.fieldKey}_revealOp`; } // prettier-ignore
+ @computed get clipHeightKey() { return `_${this._props.fieldKey}_clipHeight`; } // prettier-ignore
+ @computed get clipWidthKey() { return `_${this._props.fieldKey}_clipWidth`; } // prettier-ignore
+ @computed get clipWidth() { return NumCast(this.layoutDoc[this.clipWidthKey], 50); } // prettier-ignore
+ @computed get clipHeight() { return NumCast(this.layoutDoc[this.clipHeightKey], 200); } // prettier-ignore
+ @computed get revealOp() { return StrCast(this.layoutDoc[this.revealOpKey]); } // prettier-ignore
+ set revealOp(value:string) { this.layoutDoc[this.revealOpKey] = value; } // prettier-ignore
@computed get overlayAlternateIcon() {
return (
@@ -101,8 +78,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
})
}
style={{
- background: this.revealOp === 'hover' ? 'gray' : this.frontSide ? 'white' : 'black',
- color: this.revealOp === 'hover' ? 'black' : this.frontSide ? 'black' : 'white',
+ background: this.revealOp === 'hover' ? 'gray' : this._frontSide ? 'white' : 'black',
+ color: this.revealOp === 'hover' ? 'black' : this._frontSide ? 'black' : 'white',
display: 'inline-block',
}}>
<div key="alternate" className="formattedTextBox-flip">
@@ -117,27 +94,27 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return (
<div>
<Tooltip
- title={this.frontSide ? <div className="dash-tooltip">Flip to front side to use GPT</div> : <div className="dash-tooltip">Ask GPT to create an answer on the back side of the flashcard based on your question on the front</div>}>
- <div style={{ position: 'absolute', bottom: '3px', right: '50px', cursor: 'pointer' }} onPointerDown={e => (!this.frontSide ? this.findImageTags() : null)}>
+ title={this._frontSide ? <div className="dash-tooltip">Flip to front side to use GPT</div> : <div className="dash-tooltip">Ask GPT to create an answer on the back side of the flashcard based on your question on the front</div>}>
+ <div style={{ position: 'absolute', bottom: '3px', right: '50px', cursor: 'pointer' }} onPointerDown={() => (!this._frontSide ? this.findImageTags() : null)}>
<FontAwesomeIcon icon="lightbulb" size="xl" />
</div>
</Tooltip>
{DocCast(this.Document.embedContainer)?.type_collection === CollectionViewType.Carousel ? null : (
<div>
<Tooltip title={<div>Create a flashcard pile</div>}>
- <div style={{ position: 'absolute', bottom: '3px', right: '74px', cursor: 'pointer' }} onPointerDown={e => this.createFlashcardPile([this.Document], false)}>
+ <div style={{ position: 'absolute', bottom: '3px', right: '74px', cursor: 'pointer' }} onPointerDown={() => this.createFlashcardPile([this.Document], false)}>
<FontAwesomeIcon icon="folder-plus" size="xl" />
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">Create new flashcard stack based on text</div>}>
- <div style={{ position: 'absolute', bottom: '3px', right: '104px', cursor: 'pointer' }} onClick={e => this.gptFlashcardPile()}>
+ <div style={{ position: 'absolute', bottom: '3px', right: '104px', cursor: 'pointer' }} onClick={() => this.gptFlashcardPile()}>
<FontAwesomeIcon icon="layer-group" size="xl" />
</div>
</Tooltip>
</div>
)}
<Tooltip title={<div className="dash-tooltip">Hover to reveal</div>}>
- <div style={{ position: 'absolute', bottom: '3px', right: '25px', cursor: 'pointer' }} onClick={e => this.handleHover()}>
+ <div style={{ position: 'absolute', bottom: '3px', right: '25px', cursor: 'pointer' }} onClick={() => this.handleHover()}>
<FontAwesomeIcon color={this.revealOp === 'hover' ? 'blue' : 'black'} icon="hand-point-up" size="xl" />
</div>
</Tooltip>
@@ -147,40 +124,37 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@action handleHover = () => {
if (this.revealOp === 'hover') {
- this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip';
+ this.revealOp = 'flip';
this.Document.forceActive = false;
} else {
- this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover';
+ this.revealOp = 'hover';
this.Document.forceActive = true;
}
};
@action handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
this._inputValue = e.target.value;
- console.log(this._inputValue);
};
@action activateContent = () => {
- this.childActive = true;
+ this._childActive = true;
};
@action handleRenderClick = () => {
- this.frontSide = !this.frontSide;
+ this._frontSide = !this._frontSide;
};
@action handleRenderGPTClick = async () => {
const phonTrans = DocCast(this.Document.audio) ? DocCast(this.Document.audio).phoneticTranscription : undefined;
if (phonTrans) {
this._inputValue = StrCast(phonTrans);
- console.log('INPUT:' + this._inputValue);
this.askGPTPhonemes(this._inputValue);
} else if (this._inputValue) this.askGPT(GPTCallType.QUIZ);
- this.frontSide = false;
+ this._frontSide = false;
this._outputValue = '';
};
- @action
- private onPointerMove = ({ movementX }: PointerEvent) => {
+ onPointerMove = ({ movementX }: PointerEvent) => {
const width = movementX * this.ScreenToLocalBoxXf().Scale + (this.clipWidth / 100) * this._props.PanelWidth();
if (width && width > 5 && width < this._props.PanelWidth()) {
this.layoutDoc[this.clipWidthKey] = (width * 100) / this._props.PanelWidth();
@@ -190,12 +164,16 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
componentDidMount() {
this._props.setContentViewBox?.(this);
- reaction(
+ this._reactDisposer = reaction(
() => this._props.isSelected(), // when this reaction should update
- selected => !selected && (this.childActive = false) // what it should update to
+ selected => !selected && (this._childActive = false) // what it should update to
);
}
+ componentWillUnmount() {
+ this._reactDisposer?.();
+ }
+
protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
this._disposers[disposerId]?.();
if (ele) {
@@ -248,7 +226,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.dataDoc[which] = undefined;
return true;
}
- console.log('FALSE');
return false;
};
@@ -278,7 +255,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
remDoc1 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true);
remDoc2 = (docs: Doc | Doc[]) => toList(docs).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true);
- private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
+ registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
if (e.button !== 2) {
setupMoveUpEvents(
this,
@@ -287,7 +264,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
emptyFunction,
action((moveEv, doubleTap) => {
if (doubleTap) {
- this.childActive = true;
+ this._childActive = true;
if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc);
}
@@ -295,7 +272,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
false,
undefined,
action(() => {
- if (this.childActive) return;
+ if (this._childActive) return;
this._animating = 'all 200ms';
// on click, animate slider movement to the targetWidth
this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
@@ -315,8 +292,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
* Set up speech to text tool.
*/
setListening = () => {
- const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
- if (SpeechRecognition) {
+ if (this.SpeechRecognition) {
this.recognition.continuous = true;
this.recognition.interimResults = true;
this.recognition.lang = 'en-US';
@@ -351,20 +327,20 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
case 'fr-FR': return 'French'; //prettier-ignore
case 'it-IT': return 'Italian'; //prettier-ignore
case 'zh-CH': return 'Mandarin Chinese'; //prettier-ignore
- case 'ja': return 'Japanese'; //prettier-ignore
- default: return 'Korean'; //prettier-ignore
+ case 'ja': return 'Japanese'; //prettier-ignore
+ default: return 'Korean'; //prettier-ignore
}
};
openContextMenu = (x: number, y: number, evalu: boolean) => {
ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: 'English', event: e => this.setLanguage('en-US', 0), icon: 'question' }); //prettier-ignore
- ContextMenu.Instance.addItem({ description: 'Spanish', event: e => this.setLanguage('es-ES', 1 ), icon: 'question'}); //prettier-ignore
- ContextMenu.Instance.addItem({ description: 'French', event: e => this.setLanguage('fr-FR', 2), icon: 'question' }); //prettier-ignore
- ContextMenu.Instance.addItem({ description: 'Italian', event: e => this.setLanguage('it-IT', 3), icon: 'question' }); //prettier-ignore
- if (!evalu) ContextMenu.Instance.addItem({ description: 'Mandarin Chinese', event: e => this.setLanguage('zh-CH', 4), icon: 'question' }); //prettier-ignore
- ContextMenu.Instance.addItem({ description: 'Japanese', event: e => this.setLanguage('ja', 5), icon: 'question' }); //prettier-ignore
- ContextMenu.Instance.addItem({ description: 'Korean', event: e => this.setLanguage('ko', 6), icon: 'question' }); //prettier-ignore
+ ContextMenu.Instance.addItem({ description: 'English', event: () => this.setLanguage('en-US', 0), icon: 'question' }); //prettier-ignore
+ ContextMenu.Instance.addItem({ description: 'Spanish', event: () => this.setLanguage('es-ES', 1 ), icon: 'question'}); //prettier-ignore
+ ContextMenu.Instance.addItem({ description: 'French', event: () => this.setLanguage('fr-FR', 2), icon: 'question' }); //prettier-ignore
+ ContextMenu.Instance.addItem({ description: 'Italian', event: () => this.setLanguage('it-IT', 3), icon: 'question' }); //prettier-ignore
+ if (!evalu) ContextMenu.Instance.addItem({ description: 'Mandarin Chinese', event: () => this.setLanguage('zh-CH', 4), icon: 'question' }); //prettier-ignore
+ ContextMenu.Instance.addItem({ description: 'Japanese', event: () => this.setLanguage('ja', 5), icon: 'question' }); //prettier-ignore
+ ContextMenu.Instance.addItem({ description: 'Korean', event: () => this.setLanguage('ko', 6), icon: 'question' }); //prettier-ignore
ContextMenu.Instance.displayMenu(x, y);
};
@@ -430,7 +406,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
newCol['x'] = this.layoutDoc['x'];
newCol['y'] = NumCast(this.layoutDoc['y']) + 50;
newCol.type_collection = 'carousel';
- for (var i = 0; i < collectionArr.length; i++) {
+ for (let i = 0; i < collectionArr.length; i++) {
DocCast(collectionArr[i])[DocData].embedContainer = newCol;
}
@@ -448,10 +424,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
* Transfers the content of flashcards into a flashcard pile.
*/
gptFlashcardPile = async () => {
- var text = await this.askGPT(GPTCallType.STACK);
- var senArr = text?.split('Question: ');
- var collectionArr: Doc[] = [];
- for (let i = 1; i < senArr?.length!; i++) {
+ const text = await this.askGPT(GPTCallType.STACK);
+ const senArr = text?.split('Question: ') ?? [];
+ const collectionArr: Doc[] = [];
+ for (let i = 1; i < senArr.length; i++) {
const newDoc = Docs.Create.ComparisonDocument(senArr![i], { _layout_isFlashcard: true, _width: 300, _height: 300 });
if (StrCast(senArr![i]).includes('Keyword: ')) {
@@ -493,11 +469,10 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
* Calls GPT for each flashcard type.
*/
askGPT = async (callType: GPTCallType): Promise<string | undefined> => {
- let questionText = 'Question: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
+ const questionText = 'Question: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
const rubricText = ' Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
const queryText = questionText + ' UserAnswer: ' + this._inputValue + '. ' + rubricText;
this._loading = true;
- const doc = DocCast(this.dataDoc[this.props.fieldKey + '_0']);
if (callType == GPTCallType.CHATCARD) {
if (StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text) === '') {
@@ -517,7 +492,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res;
} else if (callType == GPTCallType.QUIZ) {
console.log(this._inputValue);
- this.frontSide = true;
+ this._frontSide = true;
this._outputValue = res.replace(/UserAnswer/g, "user's answer").replace(/Rubric/g, 'rubric');
} else if (callType === GPTCallType.FLASHCARD) {
this._loading = false;
@@ -526,7 +501,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this._loading = false;
return res;
} catch (err) {
- console.error('GPT call failed');
+ console.error('GPT call failed', err);
}
this._loading = false;
};
@@ -538,7 +513,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (c?.length === 0) await this.askGPT(GPTCallType.CHATCARD);
if (c) {
this._loading = true;
- for (let i of c) {
+ for (const i of c) {
console.log(i);
if (i.className !== 'ProseMirror-separator') await this.getImageDesc(i.src);
}
@@ -664,17 +639,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = response;
} catch (error) {
- console.log('Error');
+ console.log('Error', error);
}
};
@action
flipFlashcard = () => {
- this.frontSide = !this.frontSide;
+ this._frontSide = !this._frontSide;
};
hoverFlip = (side: boolean) => {
- if (this.revealOp === 'hover') this.frontSide = side;
+ if (this.revealOp === 'hover') this._frontSide = side;
};
testForTextFields = (whichSlot: string) => {
@@ -703,6 +678,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return layoutTemplateString;
};
+ childActiveFunc = () => this._childActive;
+
render() {
const clearButton = (which: string) => (
<Tooltip title={<div className="dash-tooltip">remove</div>}>
@@ -723,10 +700,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return targetDoc || layoutString ? (
<>
<DocumentView
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
showTags={undefined}
- fitWidth={undefined}
+ fitWidth={undefined} // set to returnTrue to make images fill the comparisonBox-- should be a user option
ignoreUsePath={layoutString ? true : undefined}
renderDepth={this.props.renderDepth + 1}
LayoutTemplateString={layoutString}
@@ -734,15 +710,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
containerViewPath={this._props.docViewPath}
moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2}
- NativeWidth={this.layoutWidth}
- NativeHeight={this.layoutHeight}
- isContentActive={() => this.childActive}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ isContentActive={this.childActiveFunc}
isDocumentActive={returnFalse}
dontSelect={returnTrue}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- styleProvider={this.childActive ? this._props.styleProvider : this.docStyleProvider}
+ styleProvider={this._childActive ? this._props.styleProvider : this.docStyleProvider}
hideLinkButton
- pointerEvents={this.childActive ? undefined : returnNone}
+ pointerEvents={this._childActive ? undefined : returnNone}
/>
{!this.Document._layout_isFlashcard ? clearButton(whichSlot) : null}
</>
@@ -759,7 +735,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
style={{ width: this._props.PanelWidth() }}
onPointerDown={e => {
this.registerSliding(e, cover);
- this.activateContent();
+ this.Document._layout_isFlashcard && this.activateContent();
}}
ref={ele => this.createDropTarget(ele, which, index)}>
{!this._isEmpty ? displayDoc(which) : null}
@@ -767,7 +743,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
if (this.Document._layout_isFlashcard) {
- const side = this.frontSide ? 1 : 0;
+ const side = this._frontSide ? 1 : 0;
// add text box to each side when comparison box is first created
if (!this.dataDoc[this.fieldKey + '_0'] && !this._isEmpty) {
@@ -790,14 +766,14 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<p style={{ display: text === '' ? 'flex' : 'none', color: 'white', marginLeft: '10px' }}>Return to all flashcards and add text to both sides. </p>
<div className="input-box">
<textarea
- value={this.frontSide ? this._outputValue : this._inputValue}
+ value={this._frontSide ? this._outputValue : this._inputValue}
onChange={this.handleInputChange}
onScroll={e => {
e.stopPropagation();
e.preventDefault();
}}
placeholder={!this.layoutDoc[`_${this._props.fieldKey}_usePath`] ? 'Enter a response for GPT to evaluate.' : ''}
- readOnly={this.frontSide}></textarea>
+ readOnly={this._frontSide}></textarea>
{this._loading ? (
<div className="loading-spinner" style={{ position: 'absolute' }}>
@@ -829,7 +805,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
Evaluate Pronunciation
</button>
- {!this.frontSide ? (
+ {!this._frontSide ? (
<button className="submit-buttonsubmit" type="button" onClick={this.handleRenderGPTClick} style={{ borderRadius: '2px', marginBottom: '3px', width: '100%' }}>
Submit
</button>
@@ -848,13 +824,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return (
<div
className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */
- style={{ display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
- onMouseEnter={() => {
- this.hoverFlip(false);
- }}
- onMouseLeave={() => {
- this.hoverFlip(true);
- }}>
+ style={{ display: 'flex', flexDirection: 'column' }}
+ onMouseEnter={() => this.hoverFlip(true)}
+ onMouseLeave={() => this.hoverFlip(false)}>
{displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)}
{this._loading ? (
<div className="loading-spinner" style={{ position: 'absolute' }}>
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 7179356b2..e57c9e842 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -16,7 +16,6 @@ import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
import './Chart.scss';
-// eslint-disable-next-line @typescript-eslint/no-var-requires
const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
interface TableBoxProps {
diff --git a/src/client/views/nodes/DiagramBox.scss b/src/client/views/nodes/DiagramBox.scss
index 323638bff..8a7863c14 100644
--- a/src/client/views/nodes/DiagramBox.scss
+++ b/src/client/views/nodes/DiagramBox.scss
@@ -1,5 +1,3 @@
-$searchbarHeight: 50px;
-
.DIYNodeBox {
width: 100%;
height: 100%;
@@ -23,7 +21,6 @@ $searchbarHeight: 50px;
justify-content: center;
align-items: center;
width: 100%;
- height: $searchbarHeight;
padding: 10px;
input[type='text'] {
@@ -42,7 +39,6 @@ $searchbarHeight: 50px;
justify-content: center;
align-items: center;
width: 100%;
- height: calc(100% - $searchbarHeight);
.diagramBox {
flex: 1;
display: flex;
diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx
index 36deb2d8d..d6c9bb013 100644
--- a/src/client/views/nodes/DiagramBox.tsx
+++ b/src/client/views/nodes/DiagramBox.tsx
@@ -18,7 +18,9 @@ import { InkingStroke } from '../InkingStroke';
import './DiagramBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
-
+/**
+ * this is a class for the diagram box doc type that can be found in the tools section of the side bar
+ */
@observer
export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
@@ -59,14 +61,21 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
{ fireImmediately: true }
);
}
+ /**
+ * helper method for renderMermaidAsync
+ * @param str string containing the mermaid code
+ * @returns
+ */
renderMermaid = (str: string) => {
try {
return mermaid.render('graph' + Date.now(), str);
- } catch (error) {
+ } catch {
return { svg: '', bindFunctions: undefined };
}
};
-
+ /**
+ * will update the div containing the mermaid diagram to render the new mermaidCode
+ */
renderMermaidAsync = async (mermaidCode: string, dashDiv: HTMLDivElement) => {
try {
const { svg, bindFunctions } = await this.renderMermaid(mermaidCode);
@@ -97,7 +106,9 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
res
);
}, 'set mermaid code');
-
+ /**
+ * will generate mermaid code with GPT based on what the user requested
+ */
generateMermaidCode = action(() => {
this._generating = true;
const prompt = 'Write this in mermaid code and only give me the mermaid code: ' + this._inputValue;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b85cb22bb..23ec42610 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -16,7 +16,7 @@ import { List } from '../../../fields/List';
import { PrefetchProxy } from '../../../fields/Proxy';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { AudioAnnoState } from '../../../server/SharedMediaTypes';
@@ -27,7 +27,7 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { dropActionType } from '../../util/DropActionTypes';
-import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter';
+import { MakeTemplate, makeUserTemplateButtonOrImage } from '../../util/DropConverter';
import { UPDATE_SERVER_CACHE } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { SearchUtil } from '../../util/SearchUtil';
@@ -1091,10 +1091,21 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
* Pins a Doc to the current presentation trail. (see TabDocView for implementation)
*/
public static PinDoc: (docIn: Doc | Doc[], pinProps: PinProps) => void;
+
+ /**
+ * Renders an image of a Doc into the Doc's icon field, then returns a promise for the image value
+ * @param doc Doc to snapshot
+ * @returns promise of icon ImageField
+ */
+ public static GetDocImage(doc: Doc) {
+ return DocumentView.getDocumentView(doc)
+ ?.ComponentView?.updateIcon?.()
+ .then(() => ImageCast(DocCast(doc).icon));
+ }
/**
* The DocumentView below the cursor at the start of a gesture (that receives the pointerDown event). Used by GestureOverlay to determine the doc a gesture should apply to.
*/
- public static DownDocView: DocumentView | undefined;
+ public static DownDocView: DocumentView | undefined; // the first DocView that receives a pointerdown event. used by GestureOverlay to determine the doc a gesture should apply to.
public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore
private _htmlOverlayEffect: Opt<Doc>;
@@ -1340,7 +1351,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
tempDoc = view.Document;
MakeTemplate(tempDoc);
Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc);
- Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButton(tempDoc));
+ Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc));
tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_user, Doc, null), 'data', tempDoc);
} else {
tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]);
@@ -1589,7 +1600,7 @@ export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.acti
export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore
export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore
export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore
-export function ActiveEraserWidth(): number { return Number(ActiveInkPen()?.eraserWidth); } // prettier-ignore
+export function ActiveEraserWidth(): number { return Number(ActiveInkPen()?.eraserWidth ?? 25); } // prettier-ignore
export function SetActiveInkWidth(width: string): void {
!isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index cb0c4d188..feaf84b7b 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
@@ -262,9 +261,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const tooltip: string = StrCast(this.Document.toolTip);
const script = ScriptCast(this.Document.onClick)?.script;
- const toggleStatus = script?.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result as boolean;
+ const toggleStatus = script?.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
+ const background = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string;
const items = DocListCast(this.dataDoc.data);
const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType));
return (
@@ -272,11 +272,11 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
tooltip={`Toggle ${tooltip}`}
type={Type.PRIM}
color={color}
+ background={background === SnappingManager.userBackgroundColor ? undefined : background}
multiSelect={true}
onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))}
- isToggle={script ? true : false}
+ isToggle={false}
toggleStatus={toggleStatus}
- //background={SnappingManager.userBackgroundColor}
label={this.label}
items={items.map(item => ({
icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />,
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 32d1e471e..31f6df2ea 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -25,7 +25,7 @@ import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
import { dropActionType } from '../../util/DropActionTypes';
import { SnappingManager } from '../../util/SnappingManager';
-import { undoBatch } from '../../util/UndoManager';
+import { undoable, undoBatch } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
@@ -195,8 +195,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const images = await this.fetchImages();
};
- @undoBatch
- drop = (e: Event, de: DragManager.DropEvent) => {
+ drop = undoable((e: Event, de: DragManager.DropEvent) => {
if (de.complete.docDragData) {
let added: boolean | undefined;
const targetIsBullseye = (ele: HTMLElement): boolean => {
@@ -225,7 +224,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return added;
}
return false;
- };
+ }, 'image drop');
@undoBatch
resolution = () => {
@@ -316,15 +315,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return cropping;
};
- /**
- * Draw image to a canvas so it can be converted to base64 and be passed into
- * GPT to be analyzed.
- * @param downX
- * @param downY
- * @param cb
- * @returns
- */
- createCanvas = async (downX?: number, downY?: number, cb?: (filename: string, x: number | undefined, y: number | undefined) => void) => {
+ createCanvas = async () => {
const canvas = document.createElement('canvas');
const scaling = 1 / (this._props.NativeDimScaling?.() || 1);
const w = AnchorMenu.Instance.marqueeWidth * scaling;
@@ -407,9 +398,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
* @param texts
*/
createBoxes = (boxes: [[[number, number]]], texts: [string]) => {
- const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1);
- const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
- for (var i = 0; i < boxes.length; i++) {
+ for (let i = 0; i < boxes.length; i++) {
const coords = boxes[i] ? boxes[i] : [];
const width = coords[1][0] - coords[0][0];
const height = coords[2][1] - coords[0][1];
@@ -446,7 +435,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this image with each question and answer labeled as "question" and "answer". Do not label each flashcard and do not include asterisks: ');
AnchorMenu.Instance.transferToFlashcard(response, NumCast(this.layoutDoc['x']), NumCast(this.layoutDoc['y']));
} catch (error) {
- console.log('Error');
+ console.log('Error', error);
}
this._loading = false;
};
@@ -461,7 +450,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const hrefBase64 = await this.createCanvas();
this.pushInfo(quizMode.NORMAL, hrefBase64);
} catch (error) {
- console.log('Error');
+ console.log('Error', error);
}
};
@@ -877,7 +866,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}}>
<CollectionFreeFormView
ref={this._ffref}
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction}
NativeWidth={returnZero}
diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js
deleted file mode 100644
index 290152cd0..000000000
--- a/src/client/views/nodes/LabelBigText.js
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
-Brorlandi/big-text.js v1.0.0, 2017
-Adapted from DanielHoffmann/jquery-bigtext, v1.3.0, May 2014
-And from Jetroid/bigtext.js v1.0.0, September 2016
-
-Usage:
-BigText("#myElement",{
- rotateText: {Number}, (null)
- fontSizeFactor: {Number}, (0.8)
- maximumFontSize: {Number}, (null)
- limitingDimension: {String}, ("both")
- horizontalAlign: {String}, ("center")
- verticalAlign: {String}, ("center")
- textAlign: {String}, ("center")
- whiteSpace: {String}, ("nowrap")
-});
-
-
-Original Projects:
-https://github.com/DanielHoffmann/jquery-bigtext
-https://github.com/Jetroid/bigtext.js
-
-Options:
-
-rotateText: Rotates the text inside the element by X degrees.
-
-fontSizeFactor: This option is used to give some vertical spacing for letters that overflow the line-height (like 'g', 'Á' and most other accentuated uppercase letters). This does not affect the font-size if the limiting factor is the width of the parent div. The default is 0.8
-
-maximumFontSize: maximum font size to use.
-
-minimumFontSize: minimum font size to use. if font is calculated smaller than this, text will be rendered at this size and wrapped
-
-limitingDimension: In which dimension the font size should be limited. Possible values: "width", "height" or "both". Defaults to both. Using this option with values different than "both" overwrites the element parent width or height.
-
-horizontalAlign: Where to align the text horizontally. Possible values: "left", "center", "right". Defaults to "center".
-
-verticalAlign: Where to align the text vertically. Possible values: "top", "center", "bottom". Defaults to "center".
-
-textAlign: Sets the text align of the element. Possible values: "left", "center", "right". Defaults to "center". This option is only useful if there are linebreaks (<br> tags) inside the text.
-
-whiteSpace: Sets whitespace handling. Possible values: "nowrap", "pre". Defaults to "nowrap". (Can also be set to enable wrapping but this doesn't work well.)
-
-Bruno Orlandi - 2017
-
-Copyright (C) 2013 Daniel Hoffmann Bernardes, Ícaro Technologies
-Copyright (C) 2016 Jet Holt
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-
-function _calculateInnerDimensions(computedStyle) {
- //Calculate the inner width and height
- var innerWidth;
- var innerHeight;
-
- var width = parseInt(computedStyle.getPropertyValue("width"));
- var height = parseInt(computedStyle.getPropertyValue("height"));
- var paddingLeft = parseInt(computedStyle.getPropertyValue("padding-left"));
- var paddingRight = parseInt(computedStyle.getPropertyValue("padding-right"));
- var paddingTop = parseInt(computedStyle.getPropertyValue("padding-top"));
- var paddingBottom = parseInt(computedStyle.getPropertyValue("padding-bottom"));
- var borderLeft = parseInt(computedStyle.getPropertyValue("border-left-width"));
- var borderRight = parseInt(computedStyle.getPropertyValue("border-right-width"));
- var borderTop = parseInt(computedStyle.getPropertyValue("border-top-width"));
- var borderBottom = parseInt(computedStyle.getPropertyValue("border-bottom-width"));
-
- //If box-sizing is border-box, we need to subtract padding and border.
- var parentBoxSizing = computedStyle.getPropertyValue("box-sizing");
- if (parentBoxSizing == "border-box") {
- innerWidth = width - (paddingLeft + paddingRight + borderLeft + borderRight);
- innerHeight = height - (paddingTop + paddingBottom + borderTop + borderBottom);
- } else {
- innerWidth = width;
- innerHeight = height;
- }
- var obj = {};
- obj["width"] = innerWidth;
- obj["height"] = innerHeight;
- return obj;
-}
-
-export default function BigText(element, options) {
-
- if (typeof element === 'string') {
- element = document.querySelector(element);
- } else if (element.length) {
- // Support for array based queries (such as jQuery)
- element = element[0];
- }
-
- var defaultOptions = {
- rotateText: null,
- fontSizeFactor: 0.8,
- maximumFontSize: null,
- limitingDimension: "both",
- horizontalAlign: "center",
- verticalAlign: "center",
- textAlign: "center",
- whiteSpace: "nowrap",
- singleLine: true
- };
-
- //Merge provided options and default options
- options = options || {};
- for (var opt in defaultOptions)
- if (defaultOptions.hasOwnProperty(opt) && !options.hasOwnProperty(opt))
- options[opt] = defaultOptions[opt];
-
- //Get variables which we will reference frequently
- var style = element.style;
- var parent = element.parentNode;
- var parentStyle = parent.style;
- var parentComputedStyle = document.defaultView.getComputedStyle(parent);
-
- //hides the element to prevent "flashing"
- style.visibility = "hidden";
- //Set some properties
- style.display = "inline-block";
- style.clear = "both";
- style.float = "left";
- var fontSize = options.maximumFontSize;
- if (options.singleLine) {
- style.fontSize = (fontSize * options.fontSizeFactor) + "px";
- style.lineHeight = fontSize + "px";
- } else {
- for (; fontSize > options.minimumFontSize; fontSize = fontSize - Math.min(fontSize / 2, Math.max(0, fontSize - 48) + 2)) {
- style.fontSize = (fontSize * options.fontSizeFactor) + "px";
- style.lineHeight = "1";
- if (element.offsetHeight <= +parentComputedStyle.height.replace("px", "")) {
- break;
- }
- }
- }
- style.whiteSpace = options.whiteSpace;
- style.textAlign = options.textAlign;
- style.position = "relative";
- style.padding = 0;
- style.margin = 0;
- style.left = "50%";
- style.top = "50%";
- var computedStyle = document.defaultView.getComputedStyle(element);
-
- //Get properties of parent to allow easier referencing later.
- var parentPadding = {
- top: parseInt(parentComputedStyle.getPropertyValue("padding-top")),
- right: parseInt(parentComputedStyle.getPropertyValue("padding-right")),
- bottom: parseInt(parentComputedStyle.getPropertyValue("padding-bottom")),
- left: parseInt(parentComputedStyle.getPropertyValue("padding-left")),
- };
- var parentBorder = {
- top: parseInt(parentComputedStyle.getPropertyValue("border-top")),
- right: parseInt(parentComputedStyle.getPropertyValue("border-right")),
- bottom: parseInt(parentComputedStyle.getPropertyValue("border-bottom")),
- left: parseInt(parentComputedStyle.getPropertyValue("border-left")),
- };
-
- //Calculate the parent inner width and height
- var parentInnerDimensions = _calculateInnerDimensions(parentComputedStyle);
- var parentInnerWidth = parentInnerDimensions["width"];
- var parentInnerHeight = parentInnerDimensions["height"];
-
- var box = {
- width: element.offsetWidth, //Note: This is slightly larger than the jQuery version
- height: element.offsetHeight,
- };
- if (!box.width || !box.height) return element;
-
-
- if (options.rotateText !== null) {
- if (typeof options.rotateText !== "number")
- throw "bigText error: rotateText value must be a number";
- var rotate = "rotate(" + options.rotateText + "deg)";
- style.webkitTransform = rotate;
- style.msTransform = rotate;
- style.MozTransform = rotate;
- style.OTransform = rotate;
- style.transform = rotate;
- //calculating bounding box of the rotated element
- var sine = Math.abs(Math.sin(options.rotateText * Math.PI / 180));
- var cosine = Math.abs(Math.cos(options.rotateText * Math.PI / 180));
- box.width = element.offsetWidth * cosine + element.offsetHeight * sine;
- box.height = element.offsetWidth * sine + element.offsetHeight * cosine;
- }
-
- var parentWidth = (parentInnerWidth - parentPadding.left - parentPadding.right);
- var parentHeight = (parentInnerHeight - parentPadding.top - parentPadding.bottom);
- var widthFactor = parentWidth / box.width;
- var heightFactor = parentHeight / box.height;
- var lineHeight;
-
- if (options.limitingDimension.toLowerCase() === "width") {
- lineHeight = Math.floor(widthFactor * fontSize);
- } else if (options.limitingDimension.toLowerCase() === "height") {
- lineHeight = Math.floor(heightFactor * fontSize);
- } else if (widthFactor < heightFactor)
- lineHeight = Math.floor(widthFactor * fontSize);
- else if (widthFactor >= heightFactor)
- lineHeight = Math.floor(heightFactor * fontSize);
-
- var fontSize = lineHeight * options.fontSizeFactor;
- if (fontSize < options.minimumFontSize) {
- parentStyle.display = "flex";
- parentStyle.alignItems = "center";
- style.textAlign = "center";
- style.visibility = "";
- style.fontSize = options.minimumFontSize + "px";
- style.lineHeight = "";
- style.overflow = "hidden";
- style.textOverflow = "ellipsis";
- style.top = "";
- style.left = "";
- style.margin = "";
- return element;
- }
- if (options.maximumFontSize && fontSize > options.maximumFontSize) {
- fontSize = options.maximumFontSize;
- lineHeight = fontSize / options.fontSizeFactor;
- }
-
- style.fontSize = Math.floor(fontSize) + "px";
- style.lineHeight = Math.ceil(lineHeight) + "px";
- style.marginBottom = "0px";
- style.marginRight = "0px";
-
- // if (options.limitingDimension.toLowerCase() === "height") {
- // //this option needs the font-size to be set already so computedStyle.getPropertyValue("width") returns the right size
- // //this +4 is to compensate the rounding erros that can occur due to the calls to Math.floor in the centering code
- // parentStyle.width = (parseInt(computedStyle.getPropertyValue("width")) + 4) + "px";
- // }
-
- //Calculate the inner width and height
- var innerDimensions = _calculateInnerDimensions(computedStyle);
- var innerWidth = innerDimensions["width"];
- var innerHeight = innerDimensions["height"];
-
- switch (options.verticalAlign.toLowerCase()) {
- case "top":
- style.top = "0%";
- break;
- case "bottom":
- style.top = "100%";
- style.marginTop = Math.floor(-innerHeight) + "px";
- break;
- default:
- style.marginTop = Math.ceil((-innerHeight / 2)) + "px";
- break;
- }
-
- switch (options.horizontalAlign.toLowerCase()) {
- case "left":
- style.left = "0%";
- break;
- case "right":
- style.left = "100%";
- style.marginLeft = Math.floor(-innerWidth) + "px";
- break;
- default:
- style.marginLeft = Math.ceil((-innerWidth / 2)) + "px";
- break;
- }
-
- //shows the element after the work is done
- style.visibility = "visible";
-
- return element;
-}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 79366f76f..058932457 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -138,7 +138,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
if (r) {
if (!r.offsetHeight || !r.offsetWidth) {
- console.log("CAN'T FIT TO EMPTY BOX");
+ //console.log("CAN'T FIT TO EMPTY BOX");
this._timeout && clearTimeout(this._timeout);
this._timeout = setTimeout(() => this.fitTextToBox(r));
return textfitParams;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index e7470c74c..816d4a3b0 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -10,7 +10,7 @@ import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, FieldValue, ImageCast, NumCast, StrCast, toList } from '../../../fields/Types';
+import { Cast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types';
import { ImageField, PdfField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction } from '../../../Utils';
@@ -173,28 +173,26 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
updateIcon = () => {
// currently we render pdf icons as text labels
const docViewContent = this.DocumentView?.().ContentDiv;
- const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
- this._pdfViewer?._mainCont.current &&
- docViewContent &&
- UpdateIcon(
- filename,
- docViewContent,
- NumCast(this.layoutDoc._width),
- NumCast(this.layoutDoc._height),
- this._props.PanelWidth(),
- this._props.PanelHeight(),
- NumCast(this.layoutDoc._layout_scrollTop),
- NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], 1),
- true,
- this.layoutDoc[Id] + '-icon',
- (iconFile: string, nativeWidth: number, nativeHeight: number) => {
- setTimeout(() => {
- this.dataDoc.icon = new ImageField(iconFile);
- this.dataDoc.icon_nativeWidth = nativeWidth;
- this.dataDoc.icon_nativeHeight = nativeHeight;
- }, 500);
- }
- );
+ const filename = this.layoutDoc[Id] + '_icon_' + new Date().getTime();
+ return !(this._pdfViewer?._mainCont.current && docViewContent)
+ ? new Promise<void>(res => res())
+ : UpdateIcon(
+ filename,
+ docViewContent,
+ NumCast(this.layoutDoc._width),
+ NumCast(this.layoutDoc._height),
+ this._props.PanelWidth(),
+ this._props.PanelHeight(),
+ NumCast(this.layoutDoc._layout_scrollTop),
+ NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], 1),
+ true,
+ this.layoutDoc[Id] + '_icon_' + new Date().getTime(),
+ (iconFile: string, nativeWidth: number, nativeHeight: number) => {
+ this.dataDoc.icon = new ImageField(iconFile);
+ this.dataDoc.icon_nativeWidth = nativeWidth;
+ this.dataDoc.icon_nativeHeight = nativeHeight;
+ }
+ );
};
componentWillUnmount() {
@@ -537,7 +535,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
@@ -553,7 +550,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
) : (
<div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this._props.select(false), true)}>
<ComponentTag
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
setContentViewBox={emptyFunction} // override setContentView to do nothing
NativeWidth={this.sidebarNativeWidthFunc}
@@ -607,7 +603,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
top: 0,
}}>
<PDFViewer
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
pdfBox={this}
sidebarAddDoc={this.sidebarAddDocument}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 4933869a7..d653b27d7 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -298,18 +298,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
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));
+ return ClientUtils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => {
+ if (returnedFilename) (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY);
+ });
}
+ return new Promise<void>(res => res());
};
- updateIcon = () => {
- const makeIcon = (returnedfilename: string) => {
+ updateIcon = () =>
+ this.Snapshot(undefined, undefined, (returnedfilename: string) => {
this.dataDoc.icon = new ImageField(returnedfilename);
this.dataDoc.icon_nativeWidth = NumCast(this.layoutDoc._width);
this.dataDoc.icon_nativeHeight = NumCast(this.layoutDoc._height);
- };
- this.Snapshot(undefined, undefined, makeIcon);
- };
+ });
// creates link for snapshot
createSnapshotLink = (imagePath: string, downX?: number, downY?: number) => {
@@ -459,7 +460,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const url = field.url.href;
const subitems: ContextMenuProps[] = [];
subitems.push({ description: 'Full Screen', event: this.FullScreen, icon: 'expand' });
- subitems.push({ description: 'Take Snapshot', event: this.Snapshot, icon: 'expand-arrows-alt' });
+ subitems.push({ description: 'Take Snapshot', event: () => this.Snapshot(), icon: 'expand-arrows-alt' });
this.Document.type === DocumentType.SCREENSHOT &&
subitems.push({
description: 'Screen Capture',
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 1fd73c226..a5788d02a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -44,7 +44,6 @@ import { LinkInfo } from './LinkDocPreview';
import { OpenWhere } from './OpenWhere';
import './WebBox.scss';
-// eslint-disable-next-line @typescript-eslint/no-var-requires
const { CreateImage } = require('./WebBoxRenderer');
@observer
@@ -145,38 +144,31 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
updateIcon = async () => {
- if (!this._iframe) return;
+ if (!this._iframe) return new Promise<void>(res => res());
const scrollTop = NumCast(this.layoutDoc._layout_scrollTop);
const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
const nativeHeight = (nativeWidth * this._props.PanelHeight()) / this._props.PanelWidth();
let htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument);
if (!htmlString) {
- htmlString = await (await fetch(ClientUtils.CorsProxy(this.webField!.href))).text();
+ htmlString = await fetch(ClientUtils.CorsProxy(this.webField!.href)).then(response => response.text());
}
this.layoutDoc.thumb = undefined;
this.Document.thumbLockout = true; // lock to prevent multiple thumb updates.
- CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop)
+ return (CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop) as Promise<string>)
.then((dataUrl: string) => {
if (dataUrl.includes('<!DOCTYPE')) {
console.log('BAD DATA IN THUMB CREATION');
return;
}
- ClientUtils.convertDataUri(dataUrl, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
- setTimeout(
- action(() => {
- this.Document.thumbLockout = false;
- this.layoutDoc.thumb = new ImageField(returnedfilename);
- this.layoutDoc.thumbScrollTop = scrollTop;
- this.layoutDoc.thumbNativeWidth = nativeWidth;
- this.layoutDoc.thumbNativeHeight = nativeHeight;
- }),
- 500
- )
- );
+ return ClientUtils.convertDataUri(dataUrl, this.layoutDoc[Id] + '_icon_' + new Date().getTime(), true, this.layoutDoc[Id] + '_icon_').then(returnedfilename => {
+ this.Document.thumbLockout = false;
+ this.layoutDoc.thumb = new ImageField(returnedfilename);
+ this.layoutDoc.thumbScrollTop = scrollTop;
+ this.layoutDoc.thumbNativeWidth = nativeWidth;
+ this.layoutDoc.thumbNativeHeight = nativeHeight;
+ });
})
- .catch((error: object) => {
- console.error('oops, something went wrong!', error);
- });
+ .catch((error: object) => console.error('oops, something went wrong!', error));
};
componentDidMount() {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 07a4d1093..865146d68 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -141,6 +141,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
set _recordingDictation(value) {
!this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? mediaState.Recording : undefined);
}
+
+ // eslint-disable-next-line no-return-assign
+ @computed get config() { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this._props); } // prettier-ignore
@computed get _recordingDictation() { return this.dataDoc?.mediaState === mediaState.Recording; } // prettier-ignore
@computed get SidebarShown() { return !!(this._showSidebar || this.layoutDoc._layout_showSidebar); } // prettier-ignore
@computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore
@@ -153,7 +156,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
@computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.dataDoc[this.sidebarKey + '_height']); } // prettier-ignore
@computed get titleHeight() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) as number || 0; } // prettier-ignore
@computed get layout_autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins); } // prettier-ignore
- @computed get config() { return FormattedTextBox.MakeConfig(this._rules = new RichTextRules(this.Document, this), this._props); } // prettier-ignore
@computed get sidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore
constructor(props: FormattedTextBoxProps) {
@@ -229,6 +231,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
setupAnchorMenu = () => {
AnchorMenu.Instance.Status = 'marquee';
AnchorMenu.Instance.gptFlashcards = this.gptPDFFlashcards;
+ AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
AnchorMenu.Instance.OnClick = () => {
!this.layoutDoc.layout_showSidebar && this.toggleSidebar();
setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created
@@ -279,6 +282,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
});
+ AnchorMenu.Instance.AddDrawingAnnotation = (drawing: Doc) => {
+ const container = DocCast(this.Document.embedContainer);
+ const docView = DocumentView.getDocumentView?.(container);
+ docView?.ComponentView?._props.addDocument?.(drawing);
+ drawing.x = NumCast(this.Document.x) + NumCast(this.Document.width);
+ drawing.y = NumCast(this.Document.y);
+ };
+
AnchorMenu.Instance.setSelectedText(window.getSelection()?.toString() ?? '');
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
@@ -663,7 +674,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
textContent += node.child(i).textContent;
i++;
}
- // eslint-disable-next-line no-cond-assign
while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
ret.push(sel);
@@ -722,7 +732,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const hr = Math.round(Date.now() / 1000 / 60 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
- // eslint-disable-next-line operator-assignment
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView)
};
@@ -987,7 +996,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
findImageTags = async () => {
const c = this.ProseRef?.getElementsByTagName('img');
if (c) {
- for (let i of c) {
+ for (const i of c) {
console.log(i);
// console.log(canvas.toDataURL());
@@ -1034,7 +1043,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
// this.Document[DocData].description = response.trim();
// return response; // Return the response
} catch (error) {
- console.log('Error');
+ console.log('Error', error);
}
// return '';
};
@@ -1195,7 +1204,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const nodes: Node[] = [];
let hadStart = start !== 0;
frag.forEach((node, index) => {
- // eslint-disable-next-line no-use-before-define
const examinedNode = findAnchorNode(node, editor);
if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) {
nodes.push(examinedNode.node);
@@ -1261,6 +1269,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
if (nh) this.layoutDoc._nativeHeight = scrollHeight;
};
+ @computed get tagsHeight() {
+ return this.DocumentView?.().showTags ? Math.max(0, 20 - Math.max(this._props.yPadding ?? 0, NumCast(this.layoutDoc._yMargin))) : 0;
+ }
+
@computed get contentScaling() {
return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
}
@@ -1288,9 +1300,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
);
this._disposers.componentHeights = reaction(
// set the document height when one of the component heights changes and layout_autoHeight is on
- () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }),
- ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => {
- const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight));
+ () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins, tagsHeight: this.tagsHeight }),
+ ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight, tagsHeight }) => {
+ const newHeight = this.contentScaling * (tagsHeight + marginsHeight + Math.max(sidebarHeight, textHeight));
if (
(!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && //
layoutAutoHeight &&
@@ -1349,7 +1361,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
action(selected => {
this.prepareForTyping();
if (FormattedTextBox._globalHighlights.has('Bold Text')) {
- // eslint-disable-next-line operator-assignment
this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed
}
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
@@ -1800,7 +1811,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const match = RTFCast(this.Document[this.fieldKey])?.Text.match(/^(@[a-zA-Z][a-zA-Z_0-9 -]*[a-zA-Z_0-9-]+)/);
if (match) {
this.dataDoc.title_custom = true;
- // eslint-disable-next-line prefer-destructuring
this.dataDoc.title = match[1]; // this triggers the collectionDockingView to publish this Doc
this.EditorView?.dispatch(this.EditorView?.state.tr.deleteRange(0, match[1].length + 1));
}
@@ -1882,7 +1892,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children && !SnappingManager.IsDragging) {
- // eslint-disable-next-line no-use-before-define
const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
const toNum = (val: string) => Number(val.replace('px', ''));
const toHgt = (node: Element): number => {
@@ -1969,7 +1978,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
return ComponentTag === CollectionStackingView ? (
<SidebarAnnos
ref={this._sidebarRef}
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
Document={this.Document}
layoutDoc={this.layoutDoc}
@@ -1989,7 +1997,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
) : (
<div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.(), false), true)}>
<ComponentTag
- // eslint-disable-next-line react/jsx-props-no-spreading
{...this._props}
ref={this._sidebarTagRef}
setContentView={emptyFunction}
@@ -2098,8 +2105,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const scale = this._props.NativeDimScaling?.() || 1;
const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
- const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
- const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
+ const paddingX = Math.max(this._props.xPadding ?? 0, NumCast(this.layoutDoc._xMargin));
+ const paddingY = Math.max(this._props.yPadding ?? 0, NumCast(this.layoutDoc._yMargin));
const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._header_height}px' >
return styleFromLayout?.height === '0px' ? null : (
<div
@@ -2118,6 +2125,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
height: `${100 / scale}%`,
}),
transition: 'inherit',
+ paddingBottom: this.tagsHeight,
// overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined,
color: this.fontColor,
fontSize: this.fontSize,
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 3ec799836..88e2e4248 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -68,10 +68,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
constructor(props: AntimodeMenuProps) {
super(props);
makeObservable(this);
- runInAction(() => (RichTextMenu._instance.menu = this));
- this.updateMenu(undefined, undefined, props, this.layoutDoc);
- this._canFade = false;
- this.Pinned = true;
+ runInAction(() => {
+ RichTextMenu._instance.menu = this;
+ this.updateMenu(undefined, undefined, props, this.layoutDoc);
+ this._canFade = false;
+ this.Pinned = true;
+ });
}
@computed get noAutoLink() {
@@ -695,7 +697,6 @@ interface RichTextMenuPluginProps {
editorProps: FormattedTextBoxProps;
}
export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> {
- // eslint-disable-next-line react/no-unused-class-component-methods
update(view: EditorView & { TextView?: FormattedTextBox }, lastState: EditorState | undefined) {
RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.layoutDoc);
}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 6d8ba9222..261eb4bb4 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -21,19 +21,16 @@ import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { ImageEditorData } from '../ImageBox';
import { OpenWhereMod } from '../OpenWhere';
import './GenerativeFill.scss';
-import Buttons from './GenerativeFillButtons';
-import { BrushHandler } from './generativeFillUtils/BrushHandler';
+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';
-
-// enum BrushStyle {
-// ADD,
-// SUBTRACT,
-// MARQUEE,
-// }
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { ImageField } from '../../../../fields/URLField';
+import { resolve } from 'url';
interface GenerativeFillProps {
imageEditorOpen: boolean;
@@ -82,6 +79,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
const parentDoc = useRef<Doc | null>(null);
const childrenDocs = useRef<Doc[]>([]);
+ // constants for image cutting
+ const cutPts = useRef<Point[]>([]);
+
// Undo and Redo
const handleUndo = () => {
const ctx = ImageUtility.getCanvasContext(canvasRef);
@@ -161,7 +161,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
x: currPoint.x - e.movementX / canvasScale,
y: currPoint.y - e.movementY / canvasScale,
};
- BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor /* , brushStyle === BrushStyle.SUBTRACT */);
+ const pts = BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, BrushType.CUT);
+ cutPts.current.push(...pts);
};
drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove);
@@ -278,7 +279,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
const maskBlob = await ImageUtility.canvasToBlob(canvasMask);
const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg);
const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
- // const res = await ImageUtility.mockGetEdit(img.src);
// create first image
if (!newCollectionRef.current) {
@@ -334,6 +334,68 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
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;
@@ -439,6 +501,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
<div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
<div className="generativeFillControls">
<h1>Image Editor</h1>
+ {/* <IconButton text="Cut out" icon={<FontAwesomeIcon icon="scissors" />} /> */}
<div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
<FormControlLabel
control={
@@ -455,7 +518,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
labelPlacement="end"
sx={{ whiteSpace: 'nowrap' }}
/>
- <Buttons getEdit={getEdit} loading={loading} onReset={handleReset} />
+ <EditButtons onClick={getEdit} loading={loading} onReset={handleReset} />
+ <CutButtons onClick={cutImage} loading={loading} onReset={handleReset} />
<IconButton color={activeColor} tooltip="close" icon={<CgClose size="16px" />} onClick={handleViewClose} />
</div>
</div>
@@ -526,6 +590,24 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
}}
/>
</div>
+ <div onPointerDown={e => e.stopPropagation()} style={{ height: 225, width: '100%', display: 'flex', justifyContent: 'center', cursor: 'pointer' }}>
+ <Slider
+ sx={{
+ '& input[type="range"]': {
+ WebkitAppearance: 'slider-vertical',
+ },
+ }}
+ orientation="vertical"
+ min={1}
+ max={500}
+ defaultValue={150}
+ size="small"
+ valueLabelDisplay="auto"
+ onChange={(e: any, val: any) => {
+ setCursorData(prev => ({ ...prev, width: val as number }));
+ }}
+ />
+ </div>
</div>
{/* Edits thumbnails */}
<div className="editsBox">
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index d1f68ee0e..fe22b273d 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -6,12 +6,12 @@ import { AiOutlineInfo } from 'react-icons/ai';
import { activeColor } from './generativeFillUtils/generativeFillConstants';
interface ButtonContainerProps {
- getEdit: () => Promise<void>;
+ onClick: () => Promise<void>;
loading: boolean;
onReset: () => void;
}
-function Buttons({ loading, getEdit, onReset }: ButtonContainerProps) {
+export function EditButtons({ loading, onClick: getEdit, onReset }: ButtonContainerProps) {
return (
<div className="generativeFillBtnContainer">
<Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
@@ -41,4 +41,32 @@ function Buttons({ loading, getEdit, onReset }: ButtonContainerProps) {
);
}
-export default Buttons;
+export function CutButtons({ loading, onClick: cutImage, onReset }: ButtonContainerProps) {
+ return (
+ <div className="generativeFillBtnContainer">
+ <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
+ {loading ? (
+ <Button
+ text="CUT IMAGE"
+ type={Type.TERT}
+ color={activeColor}
+ icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
+ iconPlacement="right"
+ onClick={() => {
+ if (!loading) cutImage();
+ }}
+ />
+ ) : (
+ <Button
+ text="CUT IMAGE"
+ type={Type.TERT}
+ color={activeColor}
+ onClick={() => {
+ if (!loading) cutImage();
+ }}
+ />
+ )}
+ <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size="16px" />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
+ </div>
+ );
+}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
index 16d529d93..8a66d7347 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
@@ -1,6 +1,12 @@
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 */) => {
@@ -14,12 +20,16 @@ export class BrushHandler {
ctx.closePath();
};
- static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string /* , erase: boolean */) => {
+ 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;
- BrushHandler.brushCircleOverlay(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx, fillColor /* , erase */);
+ 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;
};
}