diff options
Diffstat (limited to 'src/client/views/collections')
4 files changed, 38 insertions, 163 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 43464e50c..d7f4251f3 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,22 +1,22 @@ -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import * as CSS from 'csstype'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; -import * as CSS from 'csstype'; -import { ClientUtils, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; +import { ClientUtils, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { Animation, DocData } from '../../../fields/DocSymbols'; +import { Animation } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { URLField } from '../../../fields/URLField'; -import { gptImageLabel } from '../../apis/gpt/GPT'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; +import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable, UndoManager } from '../../util/UndoManager'; @@ -25,11 +25,8 @@ import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; -import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; -import { CollectionSubView, docSortings, SubCollectionViewProps } from './CollectionSubView'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { SettingsManager } from '../../util/SettingsManager'; +import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; /** * New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily @@ -42,7 +39,6 @@ import { SettingsManager } from '../../util/SettingsManager'; 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 _setCurDocScript = () => ScriptField.MakeScript('scriptContext.layoutDoc._card_curDoc=this', { scriptContext: 'any' })!; @@ -74,22 +70,7 @@ export class CollectionCardView extends CollectionSubView() { return Math.ceil(this.cardDeckWidth / this.cardWidth); } - /** - * update's gpt's doc-text list and initializes callbacks - */ - childPairStringListAndUpdateSortDesc = () => - this.childPairStringList().then(sortDesc => { - GPTPopup.Instance.setSortDesc(sortDesc.join()); - GPTPopup.Instance.onSortComplete = this.processGptOutput; - GPTPopup.Instance.onQuizRandom = this.quizMode; - }); - componentDidMount() { - this._disposers.chatVis = reaction( - () => GPTPopup.Instance.Visible, - vis => !vis && this.onGptHide() - ); - GPTPopup.Instance.setRegenerateCallback(this.Document, this.childPairStringListAndUpdateSortDesc); this._props.setContentViewBox?.(this); // if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles // when inquired from the dom (below in childScreenToLocal). When the doc is actually rendered, we need to act like the @@ -110,12 +91,7 @@ export class CollectionCardView extends CollectionSubView() { ); } - onGptHide = () => Doc.setDocFilter(this.Document, 'tags', '#chat', 'remove'); componentWillUnmount() { - GPTPopup.Instance.setSortDesc(''); - GPTPopup.Instance.onSortComplete = undefined; - GPTPopup.Instance.onQuizRandom = undefined; - GPTPopup.Instance.setRegenerateCallback(undefined, null); Object.keys(this._disposers).forEach(key => this._disposers[key]?.()); this._dropDisposer?.(); } @@ -130,7 +106,7 @@ export class CollectionCardView extends CollectionSubView() { * Circle arc size, in radians, to layout cards */ @computed get archAngle() { - return NumCast(this.layoutDoc.card_arch, 90) * (Math.PI / 180) * (this.childCards.length < this._maxRowCount ? this.childCards.length / this._maxRowCount : 1); + return NumCast(this.layoutDoc.card_arch, 90) * (Math.PI / 180) * (this.childDocsNoInk.length < this._maxRowCount ? this.childDocsNoInk.length / this._maxRowCount : 1); } /** * Spacing card rows as a percent of Doc size. 100 means rows spread out to fill 100% of the Doc vertically. Default is 60% @@ -142,7 +118,7 @@ export class CollectionCardView extends CollectionSubView() { /** * The child documents to be rendered-- everything other than ink/link docs (which are marks as being svg's) */ - @computed get childCards() { + @computed get childDocsNoInk() { return this.childLayoutPairs.filter(pair => !pair.layout.layout_isSvg); } @@ -150,7 +126,7 @@ export class CollectionCardView extends CollectionSubView() { * how much to scale down the contents of the view so that everything will fit */ @computed get fitContentScale() { - const length = Math.min(this.childCards.length, this._maxRowCount); + const length = Math.min(this.childDocsNoInk.length, this._maxRowCount); return (this.childPanelWidth() * length) / this._props.PanelWidth(); } @@ -166,19 +142,12 @@ export class CollectionCardView extends CollectionSubView() { return this._props.PanelWidth() - 2 * this.xMargin; } - /** - * When in quiz mode, randomly selects a document - */ - quizMode = () => { - this.layoutDoc._card_curDoc = this.childDocs[Math.floor(Math.random() * this.childDocs.length)]; - }; - setHoveredNodeIndex = action((index: number) => { if (!SnappingManager.IsDragging) this._hoveredNodeIndex = index; }); isSelected = (doc: Doc) => this._docRefs.get(doc)?.IsSelected; - childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, Math.max(150, this._props.PanelWidth() / (this.childCards.length > this._maxRowCount ? this._maxRowCount : this.childCards.length) / this.nativeScaling)); + childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, Math.max(150, this._props.PanelWidth() / (this.childDocsNoInk.length > this._maxRowCount ? this._maxRowCount : this.childDocsNoInk.length) / this.nativeScaling)); childPanelHeight = () => this._props.PanelHeight() * this.fitContentScale; onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this.isAnyChildContentActive(); @@ -322,10 +291,10 @@ export class CollectionCardView extends CollectionSubView() { * @returns number of cards in row that contains index */ cardsInRowThatIncludesCardIndex = (index: number) => { - if (this.childCards.length < this._maxRowCount) { - return this.childCards.length; + if (this.childDocsNoInk.length < this._maxRowCount) { + return this.childDocsNoInk.length; } - const totalCards = this.childCards.length; + const totalCards = this.childDocsNoInk.length; if (index < totalCards - (totalCards % this._maxRowCount)) { return this._maxRowCount; } @@ -389,103 +358,6 @@ export class CollectionCardView extends CollectionSubView() { : this.translateY(index); }; - /** - * A list of the text content of all the child docs. RTF documents will have just their text and pdf documents will have the first 50 words. - * Image documents are converted to bse64 and gpt generates a description for them. all other documents use their title. This string is - * inputted into the gpt prompt to sort everything together - * @returns - */ - childPairStringList = () => { - const docToText = (doc: Doc) => { - switch (doc.type) { - case DocumentType.PDF: return StrCast(doc.text).split(/\s+/).slice(0, 50).join(' '); // first 50 words of pdf text - case DocumentType.IMG: return this.getImageDesc(doc); - case DocumentType.RTF: return StrCast(RTFCast(doc.text).Text); - default: return StrCast(doc.title); - } // prettier-ignore - }; - const docTextPromises = this.childCards - .map(pair => pair.layout) - .map(async doc => { - const docText = (await docToText(doc)) ?? ''; - doc.gptInputText = docText; - this._textToDoc.set(docText.replace(/\n/g, ' ').trim(), doc); - return `======${docText.replace(/\n/g, ' ').trim()}======`; - }); - return Promise.all<string>(docTextPromises); - }; - - /** - * Calls the gpt API to generate descriptions for the images in the view - * @param image - * @returns - */ - getImageDesc = async (image: Doc) => { - if (StrCast(image.description)) return StrCast(image.description); // Return existing description - const { href } = (image.data as URLField).url; - const hrefParts = href.split('.'); - const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; - try { - const hrefBase64 = await imageUrlToBase64(hrefComplete); - const response = await gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.'); - image[DocData].description = response.trim(); - return response; // Return the response from gptImageLabel - } catch (error) { - console.log(error); - } - return ''; - }; - - /** - * Processes gpt's output depending on the type of question the user asked. Converts gpt's string output to - * usable code - * @param gptOutput - * @param questionType - * @param tag - */ - processGptOutput = (gptOutput: string, questionType: string, tag?: string) => - undoable(() => { - // Split the string into individual list items - const listItems = gptOutput.split('======').filter(item => item.trim() !== ''); - - if (questionType === '2' || questionType === '4') { - this.childDocs.forEach(d => { - TagItem.removeTagFromDoc(d, '#chat'); - }); - } - - if (questionType === '6') { - this.Document[this._props.fieldKey + '_sort'] = docSortings.Chat; - } - - listItems.forEach((item, index) => { - const normalizedItem = item.trim(); - // find the corresponding Doc in the textToDoc map - const doc = this._textToDoc.get(normalizedItem); - if (doc) { - switch (questionType) { - case '6': - doc.chatIndex = index; - break; - case '1': - if (tag) { - const hashTag = tag.startsWith('#') ? tag : '#' + tag[0].toLowerCase() + tag.slice(1); - const filterTag = Doc.MyFilterHotKeys.map(key => StrCast(key.toolType)).find(key => key.includes(tag)) ?? hashTag; - TagItem.addTagToDoc(doc, filterTag); - } - break; - case '2': - case '4': - TagItem.addTagToDoc(doc, '#chat'); - Doc.setDocFilter(this.Document, 'tags', '#chat', 'check'); - break; - } - } else { - console.warn(`No matching document found for item: ${normalizedItem}`); - } - }); - }, '')(); - childScreenToLocal = computedFn((doc: Doc, index: number, isSelected: boolean) => () => { // need to explicitly trigger an invalidation since we're reading everything from the Dom this._forceChildXf; @@ -625,7 +497,7 @@ export class CollectionCardView extends CollectionSubView() { curDoc = () => DocCast(this.layoutDoc._card_curDoc); render() { - const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale; + const fitContentScale = this.childDocsNoInk.length === 0 ? 1 : this.fitContentScale; return ( <div className="collectionCardView-outer" @@ -657,7 +529,7 @@ export class CollectionCardView extends CollectionSubView() { <div className="collectionCardView-flashcardUI" style={{ - pointerEvents: this.childCards.length === 0 ? undefined : 'none', + pointerEvents: this.childDocsNoInk.length === 0 ? undefined : 'none', height: `${100 / this.nativeScaling / fitContentScale}%`, width: `${100 / this.nativeScaling / fitContentScale}%`, transform: `scale(${this.nativeScaling * fitContentScale})`, diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5e99bec39..b40cd2761 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -37,6 +37,9 @@ export enum docSortings { Tag = 'tag', None = '', } + +export const ChatSortField = 'chat_sortIndex'; + export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) @@ -121,6 +124,7 @@ export function CollectionSubView<X>() { return this.dataDoc[this._props.fieldKey]; // this used to be 'layoutDoc', but then template fields will get ignored since the template is not a proto of the layout. hopefully nothing depending on the previous code. } + hasChildDocs = () => this.childLayoutPairs.map(pair => pair.layout); @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] { const { Document, TemplateDataDocument } = this._props; const validPairs = this.childDocs @@ -228,23 +232,21 @@ export function CollectionSubView<X>() { childSortedDocs = (docsIn: Doc[], dragIndex: number) => { const sortType = StrCast(this.Document[this._props.fieldKey + '_sort']) as docSortings; - const isDesc = BoolCast(this.Document[this._props.fieldKey + '_sort_desc']); + const isDesc = BoolCast(this.Document[this._props.fieldKey + '_sort_reverse']); const docs = docsIn.slice(); - if (sortType) { - docs.sort((docA, docB) => { - const [typeA, typeB] = (() => { - switch (sortType) { - default: - case docSortings.Type: return [StrCast(docA.type), StrCast(docB.type)]; - case docSortings.Chat: return [NumCast(docA.chatIndex, 9999), NumCast(docB.chatIndex,9999)]; - case docSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; - case docSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()]; - case docSortings.Tag: return [StrListCast(docA.tags).join(""), StrListCast(docB.tags).join("")]; - } - })(); //prettier-ignore - return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1); - }); - } + sortType && docs.sort((docA, docB) => { + const [typeA, typeB] = (() => { + switch (sortType) { + default: + case docSortings.Type: return [StrCast(docA.type), StrCast(docB.type)]; + case docSortings.Chat: return [NumCast(docA[ChatSortField], 9999), NumCast(docB[ChatSortField], 9999)]; + case docSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; + case docSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()]; + case docSortings.Tag: return [StrListCast(docA.tags).join(""), StrListCast(docB.tags).join("")]; + } + })(); + return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? -1 : 1); + }); //prettier-ignore if (dragIndex !== -1) { const draggedDoc = DragManager.docsBeingDragged[0]; const originalIndex = docs.findIndex(doc => doc === draggedDoc); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 6f0833a22..a4900e9d7 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -89,6 +89,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr TraceMobx(); if (type === undefined) return null; switch (type) { + default: + case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />; case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />; case CollectionViewType.Calendar: return <CalendarBox key="collview" {...props} />; case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />; @@ -105,8 +107,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />; case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />; case CollectionViewType.Card: return <CollectionCardView key="collview" {...props} />; - case CollectionViewType.Freeform: - default: return <CollectionFreeFormView key="collview" {...props} />; } }; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 5524fedb3..9cfb0416c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -461,6 +461,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps const newColDim = 900; for (const label of labelGroups) { const newCollection = MarqueeView.getCollection([], undefined, false, this.Bounds); + newCollection[DocData].title = label + ' Collection'; newCollection._x = this.Bounds.left + x_offset; newCollection._y = this.Bounds.top + y_offset; newCollection._width = newColDim; |
