diff options
author | alyssaf16 <alyssa_feinberg@brown.edu> | 2024-06-06 22:20:24 -0400 |
---|---|---|
committer | alyssaf16 <alyssa_feinberg@brown.edu> | 2024-06-06 22:20:24 -0400 |
commit | 7594f649890d27d558be35c9d40a0aa8211aec67 (patch) | |
tree | a37a4719f591037380e4ac9838aebf3e669bb968 | |
parent | c76505f56a83625e3993427838aaee0c54c5fb81 (diff) |
Flashcard changes - menu added
-rw-r--r-- | src/client/views/nodes/ComparisonBox.scss | 20 | ||||
-rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 99 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/FieldView.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.scss | 6 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 1 |
6 files changed, 87 insertions, 43 deletions
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index f3dc0f68c..dc107b576 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -47,6 +47,7 @@ width: '91%'; height: '80%'; z-index: '-1'; + overscroll-behavior: contain; } .clip-div { @@ -241,24 +242,5 @@ } } } - - // .loading-circle { - // position: relative; - // width: 50px; - // height: 50px; - // border-radius: 50%; - // border: 3px solid #ccc; - // border-top-color: #333; - // animation: spin 1s infinite linear; - // } - - // @keyframes spin { - // 0% { - // transform: rotate(0deg); - // } - // 100% { - // transform: rotate(360deg); - // } - // } } } diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 6d4af2f3e..f844892c5 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -1,9 +1,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, makeObservable, observable } from 'mobx'; +import { action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils'; +import { returnFalse, returnNone, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; @@ -27,6 +27,7 @@ import { FormattedTextBox } from './formattedText/FormattedTextBox'; import ReactLoading from 'react-loading'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; +import { tickStep } from 'd3'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @@ -37,7 +38,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() constructor(props: FieldViewProps) { super(props); makeObservable(this); - // this._isAnyChildContentActive = true; } @observable private _inputValue = ''; @@ -63,12 +63,19 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() @computed get clipHeight() { return NumCast(this.layoutDoc[this.clipHeightKey], 200); } + get revealOp() { + return this.layoutDoc[`_${this._props.fieldKey}_revealOp`]; + } get clipHeightKey() { return '_' + this._props.fieldKey + '_clipHeight'; } componentDidMount() { this._props.setContentViewBox?.(this); + reaction( + () => this._props.isSelected(), + selected => !selected && (this.childActive = false) + ); } protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => { this._disposers[disposerId]?.(); @@ -98,7 +105,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() emptyFunction, action((moveEv, doubleTap) => { if (doubleTap) { - this._isAnyChildContentActive = 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); } @@ -106,7 +113,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() false, undefined, action(() => { - if (this._isAnyChildContentActive) 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(); @@ -247,7 +254,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() hoverFlip = (side: string | undefined) => { if (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'hover') this.layoutDoc[`_${this._props.fieldKey}_usePath`] = side; }; - /** * Creates the button used to flip the flashcards. */ @@ -259,7 +265,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() className="formattedTextBox-alternateButton" onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => { - console.log(this.layoutDoc[`_${this._props.fieldKey}_revealOp`]); if (!this.layoutDoc[`_${this._props.fieldKey}_revealOp`] || this.layoutDoc[`_${this._props.fieldKey}_revealOp`] === 'flip') { this.flipFlashcard(); @@ -271,6 +276,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() style={{ background: usepath === 'alternate' ? 'white' : 'black', color: usepath === 'alternate' ? 'black' : 'white', + display: 'inline-block', }}> <div key="alternate" className="formattedTextBox-flip"> <FontAwesomeIcon icon="turn-up" size="1x" /> @@ -280,6 +286,37 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ); } + @computed get flashcardMenu() { + return ( + <div> + <Tooltip + title={ + this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? ( + <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</div> + ) + }> + <div style={{ position: 'absolute', bottom: '3px', right: '55px', cursor: 'pointer' }} onPointerDown={e => (!this.layoutDoc[`_${this._props.fieldKey}_usePath`] ? this.askGPT(GPTCallType.CHATCARD) : null)}> + <FontAwesomeIcon icon="lightbulb" size="xl" /> + </div> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">Hover to reveal</div>}> + <div + style={{ position: 'absolute', bottom: '3px', right: '25px', cursor: 'pointer' }} + onClick={e => (this.revealOp === 'hover' ? (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'flip') : (this.layoutDoc[`_${this._props.fieldKey}_revealOp`] = 'hover'))}> + <FontAwesomeIcon color={this.revealOp === 'hover' ? 'blue' : 'black'} icon="layer-group" size="xl" /> + </div> + </Tooltip> + {this.overlayAlternateIcon} + </div> + ); + } + + @action activateContent = () => { + this.childActive = true; + }; + @action handleRenderGPTClick = () => { // Call the GPT model and get the output this.layoutDoc[`_${this._props.fieldKey}_usePath`] = 'alternate'; @@ -320,8 +357,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() 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) { - DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = ''; + if (StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text) === '') { + this._loading = false; + return; + } this.flipFlashcard(); } try { @@ -330,7 +371,13 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() console.error('GPT call failed'); return; } - this.animateRes(0, res, callType); + // this.animateRes(0, res, callType); + if (callType == GPTCallType.CHATCARD) { + DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res; + // this.flipFlashcard(); + } + if (callType == GPTCallType.QUIZ) this._outputValue = res; + // DocCast(this.dataDoc[this.props.fieldKey + '_0'])[DocData].text = res; // this._outputValue = res; console.log(res); } catch (err) { @@ -341,10 +388,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() layoutWidth = () => NumCast(this.layoutDoc.width, 200); layoutHeight = () => NumCast(this.layoutDoc.height, 200); - specificMenu = (): void => { - const cm = ContextMenu.Instance; - cm.addItem({ description: 'Create an Answer on the Back', event: () => this.askGPT(GPTCallType.CHATCARD), icon: 'pencil' }); - }; + // specificMenu = (): void => { + // const cm = ContextMenu.Instance; + // cm.addItem({ description: 'Create an Answer on the Back', event: () => this.askGPT(GPTCallType.CHATCARD), icon: 'pencil' }); + // }; + @observable childActive = false; render() { const clearButton = (which: string) => ( @@ -378,12 +426,13 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2} NativeWidth={this.layoutWidth} NativeHeight={this.layoutHeight} - isContentActive={emptyFunction} + isContentActive={() => this.childActive} isDocumentActive={returnFalse} + dontSelect={returnTrue} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - styleProvider={this._isAnyChildContentActive ? this._props.styleProvider : this.docStyleProvider} + styleProvider={this.childActive ? this._props.styleProvider : this.docStyleProvider} hideLinkButton - pointerEvents={this._isAnyChildContentActive ? undefined : returnNone} + pointerEvents={this.childActive ? undefined : returnNone} /> <div style={{ position: 'absolute', top: '-5px', left: '2px' }}>{layoutString ? null : clearButton(whichSlot)}</div> </> // placeholder image if doc is missingleft: `${NumCast(this.layoutDoc.width, 200) - 33}px` @@ -394,7 +443,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ); }; const displayBox = (which: string, index: number, cover: number) => ( - <div className={`${index === 0 ? 'before' : 'after'}Box-cont`} key={which} style={{ width: this._props.PanelWidth() }} onPointerDown={e => this.registerSliding(e, cover)} ref={ele => this.createDropTarget(ele, which, index)}> + <div + className={`${index === 0 ? 'before' : 'after'}Box-cont`} + key={which} + style={{ width: this._props.PanelWidth() }} + onPointerDown={e => { + this.registerSliding(e, cover); + this.activateContent(); + }} + ref={ele => this.createDropTarget(ele, which, index)}> {displayDoc(which)} </div> ); @@ -431,6 +488,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() <textarea value={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate' ? this._outputValue : this._inputValue} onChange={this.handleInputChange} + onScroll={e => e.stopPropagation()} readOnly={this.layoutDoc[`_${this._props.fieldKey}_usePath`] === 'alternate'}></textarea> {this._loading ? ( @@ -457,7 +515,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() return ( <div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`} /* change className to easily disable/enable pointer events in CSS */ - onContextMenu={this.specificMenu} + // onContextMenu={this.specificMenu} style={{ display: 'flex', flexDirection: 'column' }} onMouseEnter={() => { this.hoverFlip('alternate'); @@ -473,7 +531,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() <ReactLoading type="spin" height={30} width={30} color={'blue'} /> </div> ) : null} - {this.overlayAlternateIcon} + {this.flashcardMenu} + {/* {this.overlayAlternateIcon} */} </div> ); } @@ -491,7 +550,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() left: `calc(${this.clipWidth + '%'} - 0.5px)`, cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this._props.PanelWidth() - 5) / this._props.PanelWidth() ? 'w-resize' : undefined, }} - onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ + onPointerDown={e => !this.childActive && this.registerSliding(e, this._props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ > <div className="slide-handle" /> </div> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a25249eac..2f3357791 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -503,6 +503,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document }; onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => { + if (this._props.dontSelect?.()) return; if (e && this.layoutDoc.layout_hideContextMenu && Doc.noviceMode) { e.preventDefault(); e.stopPropagation(); @@ -1378,7 +1379,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale; isSelected = () => this.IsSelected; select = (extendSelection: boolean, focusSelection?: boolean) => { - DocumentView.SelectView(this, extendSelection); + if (!this._props.dontSelect?.()) DocumentView.SelectView(this, extendSelection); if (focusSelection) { DocumentView.showDocument(this.Document, { willZoomCentered: true, diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 818d26956..138f00492 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -50,6 +50,7 @@ export interface FieldViewSharedProps { PanelHeight: () => number; isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events isContentActive: () => boolean | undefined; // whether document contents should handle pointer events + dontSelect: () => boolean | undefined; childFilters: () => string[]; childFiltersByRanges: () => string[]; styleProvider: Opt<StyleProviderFuncType>; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 8ac8c2c5f..54643b4a5 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -89,14 +89,14 @@ audiotag:hover { right: 0; bottom: 0; width: 15; - height: 15; + height: 22; cursor: default; } .formattedTextBox-flip { align-items: center; position: absolute; - right: 3px; - bottom: 1px; + right: 2px; + bottom: 4px; } .formattedTextBox-outer { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 82133a06e..3e2befb5f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -816,6 +816,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB isTargetToggler = (anchor: Doc) => BoolCast(anchor.followLinkToggle); specificContextMenu = (e: React.MouseEvent): void => { + if (this._props.dontSelect?.()) return; const cm = ContextMenu.Instance; let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span> |