aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-10-11 15:55:19 -0400
committerbobzel <zzzman@gmail.com>2024-10-11 15:55:19 -0400
commit76abb174684f2cd231a0dd9f6b71484c16e0498a (patch)
tree8d824b7218440948008281b8d63784ad38e75bcb
parent66f2b03283a1e42c48b1c16b4344b730c0a2e9f3 (diff)
fixes for quiz mode - comparisonbox renderSide fixes. scrolling doesn't propagate out of carousel or card views. fix for text with image Doc - now gets saved to UPDATE_CACHE working set.
-rw-r--r--src/client/views/collections/CollectionCardDeckView.tsx6
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx6
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx6
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx114
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx4
-rw-r--r--src/fields/Doc.ts13
-rw-r--r--src/server/GarbageCollector.ts3
7 files changed, 95 insertions, 57 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 286df30aa..14ce9d2af 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -46,6 +46,7 @@ export class CollectionCardView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [key: string]: IReactionDisposer } = {};
private _textToDoc = new Map<string, Doc>();
+ private _oldWheel: HTMLElement | null = null;
private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center)
private _clickScript = () => ScriptField.MakeScript('scriptContext._curDoc=this', { scriptContext: 'any' })!;
@@ -66,6 +67,10 @@ export class CollectionCardView extends CollectionSubView() {
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ this._oldWheel = ele;
+ // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
+ ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
};
/**
* Callback to ensure gpt's text versions of the child docs are updated
@@ -621,6 +626,7 @@ export class CollectionCardView extends CollectionSubView() {
);
});
}
+ onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
docViewProps = (): DocumentViewProps => ({
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index f2ba90c78..05be376ca 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -22,6 +22,7 @@ const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = requi
@observer
export class CollectionCarousel3DView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
+ private _oldWheel: HTMLElement | null = null;
constructor(props: SubCollectionViewProps) {
super(props);
@@ -37,6 +38,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ this._oldWheel = ele;
+ // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
+ ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
};
@computed get scrollSpeed() {
@@ -194,6 +199,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
return this.panelWidth() * (1 - index);
}
+ onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
curDoc = () => this.carouselItems[NumCast(this.layoutDoc._carousel_index)]?.layout;
answered = (correct: boolean) => (!correct || !this.curDoc()) && this.changeSlide(1);
docViewProps = () => ({
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index aa447c7bf..ef66a2c83 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -17,6 +17,7 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
export class CollectionCarouselView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
+ _oldWheel: HTMLElement | null = null;
_fadeTimer: NodeJS.Timeout | undefined;
@observable _last_index = this.carouselIndex;
@observable _last_opacity = 1;
@@ -35,6 +36,10 @@ export class CollectionCarouselView extends CollectionSubView() {
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
}
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ this._oldWheel = ele;
+ // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
+ ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
};
@computed get captionMarginX(){ return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore
@@ -91,6 +96,7 @@ export class CollectionCarouselView extends CollectionSubView() {
: this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
? false
: undefined;
+ onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => {
return (
<DocumentView
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index a57090e99..111fabca3 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -39,6 +39,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ComparisonBox, fieldKey);
}
+ static qtoken = 'Question: ';
+ static ktoken = 'Keyword: ';
+ static atoken = 'Answer: ';
private SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
private _closeRef = React.createRef<HTMLDivElement>();
private _disposers: { [key: string]: DragManager.DragDropDisposer | undefined } = {};
@@ -55,11 +58,11 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
@observable private _childActive = false;
@observable private _animating = '';
@observable private _listening = false;
- @observable private _renderSide = this.fieldKey;
+ @observable private _renderSide = this.frontKey;
@observable private _recognition = new this.SpeechRecognition();
@computed get isFlashcard() { return BoolCast(this.Document.layout_isFlashcard); } // prettier-ignore
- @computed get frontKey() { return this._props.fieldKey; } // prettier-ignore
+ @computed get frontKey() { return this._props.fieldKey + '_front'; } // prettier-ignore
@computed get backKey() { return this._props.fieldKey + '_back'; } // prettier-ignore
@computed get revealOpKey() { return `_${this._props.fieldKey}_revealOp`; } // prettier-ignore
@computed get clipHeightKey() { return `_${this._props.fieldKey}_clipHeight`; } // prettier-ignore
@@ -124,7 +127,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
</div>
</Tooltip>
)}
- {DocCast(this.Document.embedContainer)?.type_collection !== CollectionViewType.Freeform ? null : (
+ {DocCast(this.Document.embedContainer)?.type_collection !== CollectionViewType.Freeform || this._renderSide === this.backKey ? null : (
<Tooltip title={<div className="dash-tooltip">Create new flashcard stack based on text</div>}>
<div className="comparisonBox-button" onClick={this.gptFlashcardPile}>
<FontAwesomeIcon icon="layer-group" size="xl" />
@@ -150,9 +153,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
if (phonTrans) {
this._inputValue = StrCast(phonTrans);
this.askGPTPhonemes(this._inputValue);
+ this._renderSide = this.backKey;
+ this._outputValue = '';
} else if (this._inputValue) this.askGPT(GPTCallType.QUIZ);
- this._renderSide = this.backKey;
- this._outputValue = '';
};
onPointerMove = ({ movementX }: PointerEvent) => {
@@ -444,39 +447,42 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
}),
text
);
- /**
- * Transfers the content of flashcards into a flashcard pile.
- */
- gptFlashcardPile = async () => {
- this.askGPT(GPTCallType.STACK).then(text => {
- const [qtoken, ktoken, atoken] = ['Question: ', 'Keyword: ', 'Answer: '];
- const collectionArr: Doc[] = [];
- const promises = text
- .split(qtoken)
+
+ createFlashcard = (tuple: string, useDoc?: Doc) => {
+ const [ktoken, atoken] = [ComparisonBox.ktoken, ComparisonBox.atoken];
+ const newDoc = useDoc ?? Docs.Create.ComparisonDocument('', { _layout_isFlashcard: true, _width: 300, _height: 300 });
+ const question = (tuple.includes(ktoken) ? tuple.split(ktoken)[0] : tuple).split(atoken)[0];
+ const rest = tuple.replace(question, '');
+ // prettier-ignore
+ const answer = rest.startsWith(ktoken) ? // if keyword comes first,
+ tuple.includes(atoken) ? tuple.split(atoken)[1] : "" : //if tuple includes answer, split at answer and take what's left, otherwise there's no answer
+ rest.includes(ktoken) ? // otherwise if keyword is present it must come after answer,
+ rest.split(ktoken)[0].split(atoken)[1] : // split at keyword and take what comes first and split that at answer and take what's left
+ rest.replace(atoken,""); // finally if there's no keyword, just get rid of answer token and take what's left
+ const keyword = rest.replace(atoken, '').replace(answer, '').replace(ktoken, '').trim();
+ const fillInFlashcard = (img?: Doc) => {
+ newDoc[DocData][this.frontKey] = this.textCreator('question', question, img);
+ newDoc[DocData][this.backKey] = this.textCreator('answer', answer);
+ return newDoc;
+ };
+ return keyword && keyword !== 'none' ? this.fetchImages(keyword).then(img => fillInFlashcard(img)) : fillInFlashcard();
+ };
+
+ createFlashcardDeck = (text: string) => {
+ Promise.all(
+ text
+ .split(ComparisonBox.qtoken)
.filter(t => t)
- .map(tuple => {
- const newDoc = Docs.Create.ComparisonDocument('', { _layout_isFlashcard: true, _width: 300, _height: 300 });
- const question = (tuple.includes(ktoken) ? tuple.split(ktoken)[0] : tuple).split(atoken)[0];
- const rest = tuple.replace(question, '');
- // prettier-ignore
- const answer = rest.startsWith(ktoken) ? // if keyword comes first,
- tuple.includes(atoken) ? tuple.split(atoken)[1] : "" : //if tuple includes answer, split at answer and take what's left, otherwise there's no answer
- rest.includes(ktoken) ? // otherwise if keyword is present it must come after answer,
- rest.split(ktoken)[0].split(atoken)[1] : // split at keyword and take what comes first and split that at answer and take what's left
- rest.replace(atoken,""); // finally if there's no keyword, just get rid of answer token and take what's left
- const keyword = rest.replace(atoken, '').replace(answer, '').replace(ktoken, '').trim();
- const fillInFlashcard = (img?: Doc) => {
- newDoc[DocData][this.frontKey] = this.textCreator('question', question, img);
- newDoc[DocData][this.backKey] = this.textCreator('answer', answer);
- collectionArr.push(newDoc);
- };
- return keyword && keyword !== 'none' ? this.fetchImages(keyword).then(img => fillInFlashcard(img)) : fillInFlashcard();
- });
- Promise.all(promises).then(() => this.createFlashcardPile(collectionArr, true));
- });
+ .map(tuple => this.createFlashcard(tuple))
+ ).then(docs => this.createFlashcardPile(docs, true));
};
/**
+ * queries GPT about a topic and then creates a flashcard deck from the results.
+ */
+ gptFlashcardPile = () => this.askGPT(GPTCallType.STACK).then(this.createFlashcardDeck);
+
+ /**
* Calls GPT for each flashcard type.
*/
askGPT = async (callType: GPTCallType) => {
@@ -498,7 +504,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
break;
case GPTCallType.QUIZ:
runInAction(() => {
- this._renderSide = this.frontKey;
+ this._renderSide = this.backKey;
this._outputValue = res.replace(/UserAnswer/g, "user's answer").replace(/Rubric/g, 'rubric');
});
break;
@@ -728,20 +734,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
);
if (this.isFlashcard) {
- const dataSplit = StrCast(this.dataDoc.data).includes('Keyword: ') ? StrCast(this.dataDoc.data).split('Keyword: ') : StrCast(this.dataDoc.data).split('Answer: ');
-
- // add text box to each side when comparison box is first created
- if (!this.dataDoc[this.backKey] && !this._isEmpty) {
- this.dataDoc[this.backKey] = this.textCreator('answer', dataSplit[1]);
+ if (this.dataDoc.data) {
+ if (!this.dataDoc[this.backKey] || !this.dataDoc[this.frontKey]) this.createFlashcard(StrCast(this.dataDoc.data), this.Document);
+ } else {
+ // add text box to each side when comparison box is first created
+ if (!this.dataDoc[this.backKey] && !this._isEmpty) {
+ const answer = this.textCreator('answer', 'answer here');
+ this.dataDoc[this.backKey] = answer;
+ answer[DocData].text_placeholder = true;
+ }
+
+ if (!this.dataDoc[this.frontKey] && !this._isEmpty) {
+ const question = this.textCreator('question', 'hint: Enter a topic, select this document and click the stack button to have GPT create a deck of cards');
+ this.dataDoc[this.frontKey] = question;
+ question[DocData].text_placeholder = true;
+ }
}
- if (!this.dataDoc[this.frontKey] && !this._isEmpty) {
- const question = this.textCreator('question', dataSplit[0] || 'hint: Enter a topic, select this document and click the stack button to have GPT create a deck of cards');
- this.dataDoc[this.frontKey] = question;
- !dataSplit[0] && (question[DocData].text_placeholder = true);
- }
-
- if (DocCast(this.Document.embedContainer).practiceMode === practiceMode.QUIZ) {
+ if (DocCast(this.Document.embedContainer)?.practiceMode === practiceMode.QUIZ) {
const text = StrCast(RTFCast(DocCast(this.dataDoc[this.frontKey]).text)?.Text);
return (
<div className={`comparisonBox${this._props.isContentActive() ? '-interactive' : ''}`}>
@@ -749,15 +759,15 @@ 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._renderSide ? this._outputValue : this._inputValue}
+ value={this._renderSide === this.backKey ? 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._renderSide === this.frontKey}></textarea>
-
+ readOnly={this._renderSide === this.backKey}
+ />
{!this.loading ? null : (
<div className="loading-spinner">
<ReactLoading type="spin" height={30} width={30} color={'blue'} />
@@ -778,8 +788,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
<button className="submit-buttonpronunciation" onClick={this.evaluatePronunciation}>
Evaluate Pronunciation
</button>
- <button className="submit-buttonsubmit" type="button" onClick={this._renderSide === this.frontKey ? this.flipFlashcard : this.handleRenderGPTClick}>
- {this._renderSide === this.frontKey ? 'Redo the Question' : 'Submit'}
+ <button className="submit-buttonsubmit" type="button" onClick={this._renderSide === this.backKey ? this.flipFlashcard : this.handleRenderGPTClick}>
+ {this._renderSide === this.backKey ? 'Redo the Question' : 'Submit'}
</button>
</div>
</div>
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index 0304ddc86..967f4aa5b 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -68,7 +68,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try {
this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, (this.props.getPos() ?? 0) + (expand ? 2 : 1))));
- } catch (err) {
+ } catch {
/* empty */
}
}, 0);
@@ -95,7 +95,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
setTimeout(() => {
try {
this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2)));
- } catch (err) {
+ } catch {
/* empty */
}
}, 0);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 81241f9fe..45dfe233f 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -960,6 +960,19 @@ export namespace Doc {
}
} else if (field instanceof PrefetchProxy) {
Doc.FindReferences(field.value, references, system);
+ } else if (field instanceof RichTextField) {
+ const re = /"docId"\s*:\s*"(.*?)"/g;
+ let match: string[] | null;
+ while ((match = re.exec(field.Data)) !== null) {
+ const urlString = match[1];
+ if (urlString) {
+ const rdoc = DocServer.GetCachedRefField(urlString);
+ if (rdoc) {
+ references.add(rdoc);
+ Doc.FindReferences(rdoc, references, system);
+ }
+ }
+ }
}
} else if (field instanceof Promise) {
// eslint-disable-next-line no-debugger
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 041f65592..74e8c288a 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -1,7 +1,4 @@
/* eslint-disable no-await-in-loop */
-/* eslint-disable no-continue */
-/* eslint-disable no-cond-assign */
-/* eslint-disable no-restricted-syntax */
import * as fs from 'fs';
import * as path from 'path';
import { Database } from './database';