aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx4
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx240
2 files changed, 107 insertions, 137 deletions
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 10fdbf5d5..917aaaea8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -433,14 +433,14 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
* Classifies images and assigns the labels as document fields.
*/
@undoBatch
- classifyImages = action(async (e: React.MouseEvent | undefined) => {
+ classifyImages = async () => {
const groupButton = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImageGrouper);
if (groupButton) {
this._selectedDocs = this.marqueeSelect(false, DocumentType.IMG);
ImageLabelBoxData.Instance.setData(this._selectedDocs);
MainView.Instance.expandFlyout(groupButton);
}
- });
+ };
/**
* Groups images to most similar labels.
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 13eb2bf3f..cf2cf5e18 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, 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 { DocumentType } from '../../documents/DocumentTypes';
+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,46 +37,47 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ComparisonBox, fieldKey);
}
+ private _reactDisposer: IReactionDisposer | undefined;
private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
+ private _closeRef = React.createRef<HTMLDivElement>();
+
+ SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
+
+ protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
+ this._disposers[disposerId]?.();
+ if (ele) {
+ this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.internalDrop(e, dropEvent, fieldKey), this.layoutDoc);
+ }
+ };
+
constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
this.setListening();
}
+ @observable private _mathJaxConfig = { loader: { load: ['input/asciimath'] } }; // prettier-ignore
+ @observable private _transcriptElement = '';
@observable private _inputValue = '';
@observable private _outputValue = '';
@observable private _loading = false;
@observable private _isEmpty = false;
@observable private _audio: Doc = Docs.Create.TextDocument('');
- @observable _childActive = false; // whether pointer events pass through comparison box to its contents or not
- @observable _yRelativeToTop: boolean = true;
- @observable _animating = '';
- @observable mathJaxConfig = {
- loader: { load: ['input/asciimath'] },
- };
+ @observable private _childActive = false; // whether pointer events pass through comparison box to its contents or not
+ @observable private _animating = '';
@observable private _listening = false;
- @observable transcriptElement = '';
- @observable private frontSide = false;
- SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
- @observable recognition = new this.SpeechRecognition();
- _closeRef = React.createRef<HTMLDivElement>();
+ @observable private _frontSide = false;
+ @observable private _recognition = new this.SpeechRecognition();
- get revealOp() {
- return this.layoutDoc[`_${this._props.fieldKey}_revealOp`];
- }
- get clipHeightKey() {
- return '_' + this._props.fieldKey + '_clipHeight';
+ @computed get revealOpKey() {
+ return `_${this.fieldKey}_revealOp`;
}
-
- get clipWidthKey() {
- return '_' + this._props.fieldKey + '_clipWidth';
- }
-
+ @computed get clipHeightKey() { return '_' + this.fieldKey + '_clipHeight'; } // prettier-ignore
+ @computed get clipWidthKey() { return '_' + this.fieldKey + '_clipWidth'; } // prettier-ignore
+ @computed get revealOp() { return this.layoutDoc[`_${this.fieldKey}_revealOp`]; } // prettier-ignore
@computed get clipWidth() {
return NumCast(this.layoutDoc[this.clipWidthKey], 50);
}
-
@computed get clipHeight() {
return NumCast(this.layoutDoc[this.clipHeightKey], 200);
}
@@ -101,8 +95,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,8 +111,8 @@ 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={e => (!this._frontSide ? this.findImageTags() : null)}>
<FontAwesomeIcon icon="lightbulb" size="xl" />
</div>
</Tooltip>
@@ -145,26 +139,37 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
}
+ componentDidMount() {
+ this._props.setContentViewBox?.(this);
+ this._reactDisposer = reaction(
+ () => this._props.isSelected(), // when this reaction should update
+ selected => !selected && (this._childActive = false) // what it should update to
+ );
+ }
+ componentWillUnmount() {
+ this._disposers.forEach(d => d?.());
+ this._reactDisposer?.();
+ }
+
@action handleHover = () => {
if (this.revealOp === 'hover') {
- this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip';
+ this.layoutDoc[this.revealOpKey] = 'flip';
this.Document.forceActive = false;
} else {
- this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover';
+ this.layoutDoc[this.revealOpKey] = 'hover';
this.Document.forceActive = true;
}
};
- @action handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+ handleInputChange = action((e: React.ChangeEvent<HTMLTextAreaElement>) => {
this._inputValue = e.target.value;
- console.log(this._inputValue);
- };
+ });
- @action handleRenderClick = () => {
- this.frontSide = !this.frontSide;
- };
+ handleRenderClick = action(() => {
+ this._frontSide = !this._frontSide;
+ });
- @action handleRenderGPTClick = async () => {
+ handleRenderGPTClick = action(() => {
console.log('Phonetic transcription: ' + DocCast(this.Document.audio).phoneticTranscription);
const phonTrans = DocCast(this.Document.audio).phoneticTranscription;
if (phonTrans) {
@@ -172,12 +177,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
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 < this._props.PanelWidth()) {
this.layoutDoc[this.clipWidthKey] = (Math.max(0, width) * 100) / this._props.PanelWidth();
@@ -185,22 +189,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
return false;
};
- componentDidMount() {
- this._props.setContentViewBox?.(this);
- reaction(
- () => this._props.isSelected(), // when this reaction should update
- selected => !selected && (this._childActive = false) // what it should update to
- );
- }
-
- protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
- this._disposers[disposerId]?.();
- if (ele) {
- this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.internalDrop(e, dropEvent, fieldKey), this.layoutDoc);
- }
- };
-
- private internalDrop = undoable((e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
+ internalDrop = undoable((e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
if (dropEvent.complete.docDragData) {
const { droppedDocuments } = dropEvent.complete.docDragData;
const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocuments, this.Document, (doc: Doc | Doc[]) => this.addDoc(toList(doc).lastElement(), fieldKey));
@@ -244,11 +233,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (this.dataDoc[which] === doc) {
this._isEmpty = true;
// this.dataDoc[which] = 'empty';
- console.log('HEREEEE');
this.dataDoc[which] = undefined;
return true;
}
- console.log('FALSE');
return false;
};
@@ -295,18 +282,13 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
false,
undefined,
action(() => {
- 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();
- // this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight();
-
- setTimeout(
- action(() => {
- this._animating = '';
- }),
- 200
- );
+ if (!this._childActive) {
+ this._animating = 'all 200ms';
+ // on click, animate slider movement to the targetWidth
+ this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this._props.PanelWidth();
+ // this.layoutDoc[this.clipHeightKey] = (targetWidth * 100) / this._props.PanelHeight();
+ setTimeout(action(() => { this._animating = ''; }), 200); // prettier-ignore
+ }
})
);
}
@@ -315,31 +297,31 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
setListening = () => {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SpeechRecognition) {
- this.recognition.continuous = true;
- this.recognition.interimResults = true;
- this.recognition.lang = 'en-US';
- this.recognition.onresult = this.handleResult.bind(this);
+ this._recognition.continuous = true;
+ this._recognition.interimResults = true;
+ this._recognition.lang = 'en-US';
+ this._recognition.onresult = this.handleResult.bind(this);
}
ContextMenu.Instance.setLangIndex(0);
};
startListening = () => {
- this.recognition.start();
+ this._recognition.start();
this._listening = true;
};
stopListening = () => {
- this.recognition.stop();
+ this._recognition.stop();
this._listening = false;
};
setLanguage = (language: string, ind: number) => {
- this.recognition.lang = language;
+ this._recognition.lang = language;
ContextMenu.Instance.setLangIndex(ind);
};
convertAbr = () => {
- switch (this.recognition.lang) {
+ switch (this._recognition.lang) {
case 'en-US': return 'English'; //prettier-ignore
case 'es-ES': return 'Spanish'; //prettier-ignore
case 'fr-FR': return 'French'; //prettier-ignore
@@ -378,19 +360,18 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
},
});
this.Document.phoneticTranscription = response.data['transcription'];
- console.log('RESPONSE: ' + response.data['transcription']);
};
createFlashcardPile(collectionArr: Doc[], gpt: boolean) {
const newCol = Docs.Create.CarouselDocument(collectionArr, {
- _width: NumCast(this.layoutDoc['_' + this._props.fieldKey + '_width'], 250) + 50,
- _height: NumCast(this.layoutDoc['_' + this._props.fieldKey + '_width'], 200) + 50,
+ _width: NumCast(this.layoutDoc['_' + this.fieldKey + '_width'], 250) + 50,
+ _height: NumCast(this.layoutDoc['_' + this.fieldKey + '_width'], 200) + 50,
_layout_fitWidth: false,
_layout_autoHeight: true,
});
- newCol['x'] = this.layoutDoc['x'];
- newCol['y'] = NumCast(this.layoutDoc['y']) + 50;
- newCol.type_collection = 'carousel';
+ newCol.x = this.layoutDoc.x;
+ newCol.y = NumCast(this.layoutDoc.y) + 50;
+ newCol.type_collection = CollectionViewType.Carousel;
if (gpt) {
this._props.DocumentView?.()._props.addDocument?.(newCol);
@@ -403,9 +384,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
gptFlashcardPile = async () => {
- var text = await this.askGPT(GPTCallType.STACK);
- var senArr = text?.split('Question: ');
- var collectionArr: Doc[] = [];
+ let text = await this.askGPT(GPTCallType.STACK);
+ let senArr = text?.split('Question: ');
+ let collectionArr: Doc[] = [];
for (let i = 1; i < senArr?.length!; i++) {
const newDoc = Docs.Create.ComparisonDocument(senArr![i], { _layout_isFlashcard: true, _width: 300, _height: 300 });
@@ -480,6 +461,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}
this._loading = false;
};
+
layoutWidth = () => NumCast(this.layoutDoc.width, 200);
layoutHeight = () => NumCast(this.layoutDoc.height, 200);
@@ -488,8 +470,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) {
- console.log(i);
+ for (const i of c) {
if (i.className !== 'ProseMirror-separator') await this.getImageDesc(i.src);
}
this._loading = false;
@@ -527,17 +508,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this.convertAbr() +
' speech and they are not allophones of the same phoneme and they are far away from each on the ipa vowel chat and that pronunciation is not normal for the meaning of the word, note this difference and explain how it is supposed to sound. Just so you know, "i" sounds like "ee" as in "bee", not "ih" as an "lick". Interpret "ɹ" as the same as "r". Interpret "ʌ" as the same as "ə". Do not make "θ" and "f" interchangable. Do not make "n" and "ɲ" interchangable. Do not make "e" and "i" interchangable. If "ɚ", "ɔː", and "ɔ" are options for pronunciation, do not choose "ɚ". Ignore differences with colons. Ignore redundant letters and words and sounds and the splitting of words; do not mention this since there could be repeated words in the sentence. Provide a response like this: "Lets work on improving the pronunciation of "coffee." You said "cawffee," which is close, but we need to adjust the vowel sound. In American English, "coffee" is pronounced /ˈkɔːfi/, with a long "aw" sound. Try saying "kah-fee." Your intonation is good, but try putting a bit more stress on "like" in the sentence "I would like a coffee with milk." This will make your speech sound more natural. Keep practicing, and lets try saying the whole sentence again!"';
- switch (this.recognition.lang) {
- case 'en-US':
- this._outputValue = await gptAPICall(promptEng, GPTCallType.PRONUNCIATION);
- break;
- case 'es-ES':
- this._outputValue = await gptAPICall(promptSpa, GPTCallType.PRONUNCIATION);
- break;
- default:
- this._outputValue = await gptAPICall(promptAll, GPTCallType.PRONUNCIATION);
- break;
- }
+ switch (this._recognition.lang) {
+ case 'en-US': this._outputValue = await gptAPICall(promptEng, GPTCallType.PRONUNCIATION); break;
+ case 'es-ES': this._outputValue = await gptAPICall(promptSpa, GPTCallType.PRONUNCIATION); break;
+ default: this._outputValue = await gptAPICall(promptAll, GPTCallType.PRONUNCIATION); break;
+ } // prettier-ignore
};
handleResult = (e: SpeechRecognitionEvent) => {
@@ -568,8 +543,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
_height: 150,
title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
});
- imageSnapshot['x'] = this.layoutDoc['x'];
- imageSnapshot['y'] = this.layoutDoc['y'];
+ imageSnapshot.x = this.layoutDoc.x;
+ imageSnapshot.y = this.layoutDoc.y;
return imageSnapshot;
} catch (error) {
console.log(error);
@@ -578,8 +553,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
try {
- const response = await fetch(imageUrl);
- const blob = await response.blob();
+ const blob = await fetch(imageUrl).then(response => response.blob());
return new Promise((resolve, reject) => {
const reader = new FileReader();
@@ -600,14 +574,13 @@ 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;
- };
+ flipFlashcard = action(() => {
+ this._frontSide = !this._frontSide;
+ });
hoverFlip = (side: boolean) => {
if (this.revealOp === 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side;
@@ -659,7 +632,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
// whichDoc['backgroundColor'] = this.layoutDoc['backgroundColor'];
return targetDoc || layoutString ? (
- // <MathJaxContext config={this.mathJaxConfig}>
+ // <MathJaxContext config={this._mathJaxConfig}>
// <MathJax>
<>
<DocumentView
@@ -708,7 +681,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
// (!this.dataDoc[this.fieldKey + '_0'] && this.dataDoc[this._props.fieldKey + '_0'] !== 'empty')
@@ -732,14 +705,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' }}>
@@ -771,7 +744,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>
@@ -791,12 +764,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<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);
- }}>
+ onMouseEnter={() => this.hoverFlip(false)}
+ onMouseLeave={() => this.hoverFlip(true)}>
{displayBox(`${this.fieldKey}_${side === 0 ? 1 : 0}`, side, this._props.PanelWidth() - 3)}
{this._loading ? (
<div className="loading-spinner" style={{ position: 'absolute' }}>
@@ -820,6 +789,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
className="slide-bar"
style={{
left: `min(100% - 3px, calc(${this.clipWidth + '%'}))`,
+ transition: this._animating,
cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined,
}}
onPointerDown={e => this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */