import { ObservableMap, action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils'; import { Doc, DocListCast, Field, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { NumCast, ScriptCast, StrCast, BoolCast, DocCast, RTFCast, Cast} from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { StyleProp } from '../StyleProvider'; import { DocumentView } from '../nodes/DocumentView'; import './CollectionCardDeckView.scss'; import { CollectionSubView } from './CollectionSubView'; import { Transform } from '../../util/Transform'; import { LinkManager } from '../../util/LinkManager'; // import Card from 'react-bootstrap/Card'; import { DocumentType } from '../../documents/DocumentTypes'; import { forEach } from 'lodash'; import { SnappingManager } from '../../util/SnappingManager'; import { List } from '../../../fields/List'; import { gptAPICall, gptImageLabel } from '../../apis/gpt/GPT'; import { GPTCallType } from '../../apis/gpt/GPT'; import { ImageField, PdfField, URLField } from '../../../fields/URLField'; import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import { GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import { reaction } from 'mobx'; import { NumListCast } from '../../../fields/Doc'; @observer export class CollectionCardView extends CollectionSubView() { //TODO: for saving custom groups instead of string field hashmap nonsense should instead just set teh index for each child document per group as a fie;d // so like, group1Ibndex = 1 etc etc as they change. That way, if the documents change it doesnt matter since the idnexes are tied to the // documents themselves @observable hoveredNodeIndex = -1; //index to group @observable customGroupDictionary: Map[] = [new Map(), new Map(), new Map()]; @computed get myChildLayoutPairs() { let activeGroups = NumListCast(this._props.Document.visibleGroupNumbers); let currCustom = NumCast(this._props.Document.customSortNumber); // console.log("Active Groups:", activeGroups); for (let i=0; i< activeGroups.length; i++){ console.log("Active Groups" + activeGroups[i]) } // console.log("Current Custom Sort Number:", currCustom); if (activeGroups.length <= 0) { return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK); } if (StrCast(this._props.Document.cardSort).includes("custom")) { return this.childLayoutPairs.filter((l, index) => { if (l.layout.type === DocumentType.LINK) { return false; } // Get the group number for the current index from the customGroupDictionary const groupNumber = this.customGroupDictionary[currCustom].get(index); // console.log(`Index: ${index}, Group Number: ${groupNumber}`); // Check if the group number is in the active groups return groupNumber !== undefined && activeGroups.includes(groupNumber); }); } // Default return for non-custom cardSort or other cases, filtering out links return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK); } @action setHoveredNodeIndex = (index: number) => { if (!this.isSelected(index)) { this.hoveredNodeIndex = index; } }; translateHover = (index: number): number => { if (this.hoveredNodeIndex == index && !this.isSelected(index)) { return -50; } return 0; }; @action setSelectedNodeIndex = (index: number) => { const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); // console.log('goodnight'); if (SelectionManager.IsSelected(docs[index])) { // console.log('good mornings'); this.setSelectedNodeIndex(index); } }; isSelected = (index: number): boolean => { const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); return SelectionManager.IsSelected(docs[index]); }; inactiveDocs = () => { const docs = this.myChildLayoutPairs; return docs.filter(d => !SelectionManager.IsSelected(d.layout)); }; middleIndex = Math.floor(this.inactiveDocs().length / 2); constructor(props: any) { super(props); makeObservable(this); // this.rotationDegree(7); if (this._props.Document.customHashMap != undefined){ this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap)) } // this._props.Document.visibleGroupNumbers = new List([1,2,3,4]) reaction( () => this._props.Document.cardSort, (cardSort) => { if (cardSort === 'chat') { this.openChatPopup(); } } ); } @computed get mapToField(): List { const resultList = new List(); this.customGroupDictionary.forEach(map => { // Convert each map to a single string with entries formatted as "key:value" const mapString = Array.from(map.entries()) .map(([key, value]) => `${key}:${value}`) .join(','); // Join all key-value pairs with commas resultList.push(mapString); // Add the formatted string of the current map to the list }); return resultList; } getCustoms = (stringFrom: string[]): Map[] => { return stringFrom.map(s => { // Create a new Map object for each string. const map = new Map(); // Split the string by commas to get key-value pairs, then process each pair. s.split(',').forEach(pair => { const [key, value] = pair.split(':'); map.set(Number(key), Number(value)); }); return map; }); } private _dropDisposer?: DragManager.DragDropDisposer; componentWillUnmount() { this._dropDisposer?.(); } protected createDashEventsTarget = (ele: HTMLDivElement | null) => { this._dropDisposer?.(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); } }; childDocumentWidth = 600; // target width of a Doc... /** * how much to scale down the contents of the view so that everything will fit */ @computed get fitContentScale() { if (this.myChildLayoutPairs.length < this.maxRowCount) { length = this.myChildLayoutPairs.length; } else { length = this.maxRowCount; } return (this.childDocumentWidth * length) / this._props.PanelWidth(); } panelWidth = () => this.childDocumentWidth; panelHeight = (layout: Doc) => () => (2 * (this.panelWidth() * NumCast(layout._height))) / NumCast(layout._width); onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); isChildContentActive = () => (this.isContentActive() ? true : false); rotate = (amCards: number, index: number) => { // console.log(amCards + "wtf") const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2)); const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2))); // console.log(possRotate + "poss") if (amCards % 2 == 0 && possRotate == 0) { // console.log('whaddup'); return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2))); } else if (amCards % 2 == 0 && index > (amCards + 1) / 2) { // console.log("sup" + stepMag); return possRotate + stepMag; } return possRotate; }; translateY = (amCards: number, index: number, realIndex: number) => { const evenOdd = amCards % 2; const apex = (amCards - evenOdd) / 2; const stepMag = 200 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25); let rowOffset = 0; if (realIndex > this.maxRowCount - 1) { // 11 - 1 = 10 rowOffset = 400 * ((realIndex - (realIndex % this.maxRowCount)) / this.maxRowCount); } // console.log('steo' + stepMag); if (evenOdd == 1 || index < apex - 1) { // console.log('hi' + index); return Math.abs(stepMag * (apex - index)) - rowOffset; } else { if (index == apex || index == apex - 1) { return 0 - rowOffset; } return Math.abs(stepMag * (apex - index - 1)) - rowOffset; } }; // translateOverFlowY = (amCards: number, index: number) => { // if (amCards > 8 && index > amCards / 2) { // return 100; // } // return 0; // }; translateSelected = (index: number): number => { // if (this.isSelected(index)) { const middleOfPanel = this._props.PanelWidth() / 2; const scaledNodeWidth = this.panelWidth() * 1.25; // Calculate the position of the node's left edge before scaling const nodeLeftEdge = index * this.panelWidth(); // Find the center of the node after scaling const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2; // Calculate the translation needed to align the scaled node's center with the panel's center const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - scaledNodeWidth / 4; return translation; // } // return 0; }; @observable sortedDocs = [] as { layout: Doc; data: Doc }[]; @computed get sortedDocsType() { // if (this._props.Document.cardSort === 'chat'){ // this.openChatPopup() // const textDesc = await this.childPairStringList(); // GPTPopup.Instance.setSortDesc(textDesc) // } // if (this._props.Document.cardSort === 'chat' && this.sortedDocs.length === 0) { // this.smartSort(); // Trigger the sorting if it hasn't been done yet // return { docs: [] }; // Return an empty array or a loading state // } const desc = BoolCast(this.layoutDoc.sortDesc); let sorted = []; let docs = []; for (let i = 0; i < this.myChildLayoutPairs.length; i++) { //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list sorted[i] = this.myChildLayoutPairs[i]; } switch (this._props.Document.cardSort) { case 'type': // Copy and sort documents by type return this.sort(sorted, 'type', desc); case 'color': return this.sort(sorted, 'color', desc); case 'custom': return this.sort(sorted, 'custom', desc); case 'chat': return this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)); default: docs = this.myChildLayoutPairs; return { docs }; } } hexToHsv = (hex: string): [number, number, number] => { if (!hex) return [0, 0, 0]; // Default to black if hex is not defined const r = parseInt(hex.slice(1, 3), 16) / 255; const g = parseInt(hex.slice(3, 5), 16) / 255; const b = parseInt(hex.slice(5, 7), 16) / 255; const max = Math.max(r, g, b), min = Math.min(r, g, b); const d = max - min; let h: number; const s = max === 0 ? 0 : d / max; const v = max; switch (max) { case min: h = 0; break; case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; default: h = 0; break; } h /= 6; return [h, s, v]; }; sort = (docs: { layout: Doc; data: Doc }[], sortType: string, isDesc: boolean) => { docs.sort((docA, docB) => { let typeA; let typeB; switch (sortType) { case 'color': typeA = this.hexToHsv(StrCast(docA.layout.backgroundColor)) ?? ''; // If docA.type is undefined, use an empty string typeB = this.hexToHsv(StrCast(docB.layout.backgroundColor)) ?? ''; // If docB.type is undefined, use an empty string break; case 'custom': // console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)]) typeA = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docA)) ?? '9999'; // console.log(typeA + "A") typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999'; // console.log(typeB + 'b') break; case 'gpt': // console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)]) typeA = this.gptGroups.get(docA.layout) ?? '9999'; console.log(typeA + "typea") typeB = this.gptGroups.get(docB.layout) ?? '9999'; console.log(typeB + "typeB") // console.log(typeA + "A") // typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999'; // console.log(typeB + 'b') break; default: typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string break; } // Perform a basic string comparison if types are strings let out = 0; if (typeA < typeB) out = -1; if (typeA > typeB) out = 1; if (isDesc) out *= -1; // Reverse the sort order if descending is true return out; }); return { docs }; }; // @computed get cards(){ // } displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => { return ( r?.ContentDiv && this.docRefs.set(childPair.layout, r))} Document={childPair.layout} TemplateDataDocument={childPair.data} // onClickScript={this.toggleIcon} //suppressSetHeight={true} NativeWidth={returnZero} NativeHeight={returnZero} layout_fitWidth={returnFalse} onDoubleClickScript={this.onChildDoubleClick} renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} // focus={this.focus} ScreenToLocalTransform={screenToLocalTransform} //makes sure the box wrapper thing is in the right spot isContentActive={this.isChildContentActive} isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight(childPair.layout)} /> ); }; @observable docRefs = new ObservableMap(); @observable maxRowCount = 10; overflowAmCardsCalc(index: number) { if (this.inactiveDocs().length < this.maxRowCount) { return this.inactiveDocs().length; } // 13 - 3 = 10 const totalCards = this.inactiveDocs().length; // if 9 or less if (index < totalCards - (totalCards % 10)) { return this.maxRowCount; } //(3) return totalCards % 10; } overflowIndexCalc(realIndex: number) { if (realIndex < 10) { return realIndex; } return realIndex % 10; } translateOverflowX(realIndex: number, calcIndex: number, calcRowCards: number) { let trans = 0; // trans += (calcIndex * this.panelWidth()) + this.panelWidth() // return trans if (realIndex < this.maxRowCount) { return 0; } return (10 - calcRowCards) * (this.panelWidth() / 2); } calculateTranslateY(isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) { let trans = 0; if (isHovered) { trans += this.translateHover(realIndex); } trans += this.translateY(amCards, calcRowIndex, realIndex); if (isSelected) { trans = 50 * this.fitContentScale; } return trans; } @observable _forceChildXf = false; @computed get contentSorted() { const sortedDocs = this.sortedDocsType.docs; // const amCards = this.inactiveDocs().length; // Map sorted documents to their rendered components return sortedDocs.map((childPair, index) => { // const childPair = { layout: doc, data: doc }; const isHovered = this.hoveredNodeIndex === index; // const childPairIndex = this.myChildLayoutPairs.indexOf(childPair); const childPairIndex = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK).indexOf(childPair) const realIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair); const calcRowIndex = this.overflowIndexCalc(realIndex); const amCards = this.overflowAmCardsCalc(realIndex); const isSelected = SelectionManager.IsSelected(childPair.layout); const childScreenToLocal = () => { this._forceChildXf; const dref = this.docRefs.get(childPair.layout); const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv); return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); }; return (
{ SnappingManager.SetIsResizing(this.Document); setTimeout( action(() => { SnappingManager.SetIsResizing(undefined); this._forceChildXf = !this._forceChildXf; }), 700 ); }} style={{ width: this.panelWidth(), height: this.panelHeight(childPair.layout)(), transform: ` translateY(${this.calculateTranslateY(isHovered, isSelected, realIndex, amCards, calcRowIndex)}px) translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, calcRowIndex, amCards)}px) rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg) scale(${isSelected ? 1.25 : 1}) `, }} onMouseEnter={() => this.setHoveredNodeIndex(index)}> {this.displayDoc(childPair, childScreenToLocal)} {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex) : ''} {this._props.Document.cardSort == 'chat' ? this.renderButtons(childPairIndex, childPair.layout, true) : ''}
); }); } @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc?: Doc) { if (!isChat) { this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID); this._props.Document.customHashMap = this.mapToField; } if (isChat && doc) { this.gptGroups.set(doc, buttonID); } // console.log(`Toggled button for childPairIndex: ${childPairIndex}, buttonID: ${buttonID}, isChat: ${isChat}`); // console.log(`Updated customGroupDictionary:`, this.customGroupDictionary); if (isChat && doc) { console.log(`Updated gptGroups for doc ${doc}:`, this.gptGroups.get(doc)); } } async childPairStringList(): Promise { let string = ""; for (let i = 0; i < this.childDocs.length; i++) { switch (this.childDocs[i].type) { case DocumentType.IMG: string += `======${await this.getImageDesc(this.childDocs[i])}======`; break; case DocumentType.PDF: let pdfText = StrCast(this.childDocs[i].text); let words = pdfText.split(/\s+/); let first50Words = words.slice(0, 50); string += `======${first50Words.join(' ')}======`; this.textToDoc.set(first50Words.join(' ').trim(), this.childDocs[i]); break; case DocumentType.RTF: let rtfText = StrCast((RTFCast(this.childDocs[i].text)).Text); string += `======${StrCast((RTFCast(this.childDocs[i].text)).Text)}======`; this.textToDoc.set(rtfText.trim(), this.childDocs[i]); break; default: string += `======${StrCast(this.childDocs[i].title)}======`; this.textToDoc.set(StrCast(this.childDocs[i].title).trim(), this.childDocs[i]); } } return string; } @observable isLoading = false imageUrlToBase64 = async (imageUrl: string): Promise => { try { const response = await fetch(imageUrl); const blob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => resolve(reader.result as string); reader.onerror = error => reject(error); }); } catch (error) { console.error('Error:', error); throw error; } }; textToDoc = new Map() gptProccessedImages = new Set() @action async getImageDesc(image: Doc) { if (this.gptProccessedImages.has(image)) { // Return the already processed description return Array.from(this.textToDoc.keys()).find(key => this.textToDoc.get(key) === image) || ''; } let href = (image['data'] as URLField).url.href; let hrefParts = href.split('.'); let hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; try { let hrefBase64 = await this.imageUrlToBase64(hrefComplete); let response = await gptImageLabel(hrefBase64); this.textToDoc.set(response.trim(), image); this.gptProccessedImages.add(image); console.log(response); return response; // Return the response from gptImageLabel } catch (error) { console.log("bad things have happened"); } } //a set of all the images that have already been given desc //a map from the text to the Doc (for everything) // @action async smartSort() { // this.isLoading = true; // console.log("loading"); // // Store the result of childPairStringList in a variable // const childPairStrings = await this.childPairStringList(); // if (childPairStrings === "") { // console.log("no child pairs :("); // } else { // console.log(childPairStrings + " og list"); // let prompt = childPairStrings; // let res = await gptAPICall(prompt, GPTCallType.SORT); // this.isLoading = false; // if (res == 'Error connecting with API.') { // // If GPT call failed // console.error('GPT call failed'); // } else if (res != null) { // console.log(res); // this.processGptOutput(res); // // Update the observable with the sorted documents // this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs; // } // this.isLoading = false; // } // } gptGroups = new ObservableMap @observable amGPTGroups = 0 // Method to convert the GPT-produced string into a map processGptOutput(gptOutput: string) { // Split the string into individual list items const listItems = gptOutput.split('======').filter(item => item.trim() !== ''); this.amGPTGroups = listItems.length listItems.forEach((item, index) => { // Split the item by '~~~~~~' to get all descriptors const parts = item.split('~~~~~~').map(part => part.trim()); parts.forEach(part => { console.log(part + "part") // Find the corresponding Doc in the textToDoc map if (this.textToDoc.has(part)) { const doc = this.textToDoc.get(part); console.log("hewoooooo") if (doc) { console.log('hi') this.gptGroups.set(doc, index); } } }); }); } @observable isChatPopupOpen = false; @action openChatPopup = async () => { this.isChatPopupOpen = true; GPTPopup.Instance.setVisible(true); GPTPopup.Instance.setMode(GPTPopupMode.SORT); // Await the promise to get the string result const sortDesc = await this.childPairStringList(); GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded GPTPopup.Instance.setSortDesc(sortDesc); GPTPopup.Instance.onSortComplete = this.handleGptSortResult; }; @action handleGptSortResult = (sortResult: string) => { this.processGptOutput(sortResult); this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs; }; renderButtons(childPairIndex: number, doc?: Doc, isChat = false) { const buttons = []; // Array to hold the button elements const groupNumber = NumCast(this._props.Document.customSortNumber); let amButtons = 4; // Adjusted to your context let activeButtonIndex = this.customGroupDictionary[groupNumber].get(childPairIndex); if (isChat && doc) { if (this.amGPTGroups > 4){ amButtons = this.amGPTGroups; } activeButtonIndex = this.gptGroups.get(doc); } console.log("childPairIndex:", childPairIndex, "activeButtonIndex:", activeButtonIndex, "groupNumber:", groupNumber); for (let i = 0; i < amButtons; i++) { const isActive = activeButtonIndex === i; console.log(`Rendering button ${i} for childPairIndex ${childPairIndex} isActive: ${isActive}`); buttons.push( ); } const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6; return (
{buttons}
); } @computed get translateWrapperX() { let translate = 0; if (this.inactiveDocs().length != this.myChildLayoutPairs.length && this.inactiveDocs().length < 10) { translate += this.panelWidth() / 2; } // console.log('in' + this.inactiveDocs().length); // console.log('max' + this.maxRowCount); // if (this.inactiveDocs().length > this.maxRowCount){ // translate += ((this.inactiveDocs().length % this.maxRowCount) * this.panelWidth()) / 2 // console.log("trans" + (this.inactiveDocs().length % this.maxRowCount) / 2 ) // } return translate; } // @computed get renderCardsSort(){ // if (StrCast(this._props.Document.cardSort) == "type"){ // return this.contentSorted // } // else{ // return this.content // } // } render() { return (
this.setHoveredNodeIndex(-1)}> {this.contentSorted}
); } }