From b791fc559112b5972211a5382d07ce15ad07132a Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 3 Oct 2024 11:50:27 -0400 Subject: fixed tagsView height when zooming. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 5db854b69..70e25d119 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1195,7 +1195,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Fri, 4 Oct 2024 18:39:15 -0400 Subject: cleaning up CardDeckView so that animations work smoothly --- src/client/views/Main.tsx | 2 - .../views/collections/CollectionCalendarView.tsx | 99 ---------- .../views/collections/CollectionCardDeckView.tsx | 199 +++++++++------------ src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 - src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/FieldView.tsx | 3 +- src/client/views/nodes/calendarBox/CalendarBox.tsx | 2 +- 8 files changed, 88 insertions(+), 222 deletions(-) delete mode 100644 src/client/views/collections/CollectionCalendarView.tsx (limited to 'src') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 73d2872d1..17eea585a 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -18,7 +18,6 @@ import { TrackMovements } from '../util/TrackMovements'; import { KeyManager } from './GlobalKeyHandler'; import { InkingStroke } from './InkingStroke'; import { MainView } from './MainView'; -import { CollectionCalendarView } from './collections/CollectionCalendarView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionView } from './collections/CollectionView'; import { TabDocView } from './collections/TabDocView'; @@ -128,7 +127,6 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, - CollectionCalendarView, CollectionView, WebBox, KeyValueBox, diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx deleted file mode 100644 index 0ea9f8ebc..000000000 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { computed, makeObservable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { emptyFunction } from '../../../Utils'; -import { dateRangeStrToDates, returnTrue } from '../../../ClientUtils'; -import { Doc, DocListCast } from '../../../fields/Doc'; -import { StrCast } from '../../../fields/Types'; -import { CollectionStackingView } from './CollectionStackingView'; -import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; - -@observer -export class CollectionCalendarView extends CollectionSubView() { - constructor(props: SubCollectionViewProps) { - super(props); - makeObservable(this); - } - - componentDidMount(): void {} - - componentWillUnmount(): void {} - - @computed get allCalendars() { - return this.childDocs; // returns a list of docs (i.e. calendars) - } - - removeCalendar = () => {}; - - addCalendar = (/* doc: Doc | Doc[], annotationKey?: string | undefined */): boolean => - // bring up calendar modal with option to create a calendar - true; - - _stackRef = React.createRef(); - - panelHeight = () => this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title - - // most recent calendar should come first - sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => { - const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range); - const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range); - - const { start: aFromDate, end: aToDate } = dateRangeStrToDates(aDateRangeStr); - const { start: bFromDate, end: bToDate } = dateRangeStrToDates(bDateRangeStr); - - if (aFromDate > bFromDate) { - return -1; // a comes first - } - if (aFromDate < bFromDate) { - return 1; // b comes first - } - // start dates are the same - if (aToDate > bToDate) { - return -1; // a comes first - } - if (aToDate < bToDate) { - return 1; // b comes first - } - return 0; // same start and end dates - }; - - screenToLocalTransform = () => - this._props - .ScreenToLocalTransform() - .translate(Doc.NativeWidth(this.Document), 0) - .scale(this._props.NativeDimScaling?.() || 1); - - get calendarsKey() { - return this._props.fieldKey; - } - - render() { - return ( -
- -
- ); - } -} diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 101cc8082..a9ab9de26 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -2,7 +2,6 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils'; -import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -13,7 +12,6 @@ import { gptImageLabel } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable } from '../../util/UndoManager'; @@ -23,12 +21,12 @@ import { DocumentView } from '../nodes/DocumentView'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { computedFn } from 'mobx-utils'; enum cardSortings { Time = 'time', Type = 'type', Color = 'color', - Custom = 'custom', Chat = 'chat', Tag = 'tag', None = '', @@ -46,14 +44,13 @@ export class CollectionCardView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [key: string]: IReactionDisposer } = {}; private _textToDoc = new Map(); - private _dropped = false; // indicate when a card doc has just moved; + 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) @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap(); @observable _maxRowCount = 10; @observable _docDraggedIndex: number = -1; - @observable overIndex: number = -1; static imageUrlToBase64 = async (imageUrl: string): Promise => { try { @@ -101,7 +98,6 @@ export class CollectionCardView extends CollectionSubView() { componentDidMount() { this._props.setContentViewBox?.(this); - // Reaction to cardSort changes this._disposers.sort = reaction( () => GPTPopup.Instance.visible, isVis => { @@ -135,8 +131,7 @@ export class CollectionCardView extends CollectionSubView() { } /** - * The child documents to be rendered-- either all of them except the Links or the docs in the currently active - * custom group + * The child documents to be rendered-- everything other than ink/link docs (which are marks as being svg's) */ @computed get childDocsWithoutLinks() { return this.childDocs.filter(l => !l.layout_isSvg); @@ -155,8 +150,7 @@ export class CollectionCardView extends CollectionSubView() { */ quizMode = () => { const randomIndex = Math.floor(Math.random() * this.childDocs.length); - SelectionManager.DeselectAll(); - DocumentView.SelectView(DocumentView.getDocumentView(this.childDocs[randomIndex]), false); + DocumentView.getDocumentView(this.childDocs[randomIndex])?.select(false); }; /** @@ -168,29 +162,15 @@ export class CollectionCardView extends CollectionSubView() { @action setHoveredNodeIndex = (index: number) => { - if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) { - this._hoveredNodeIndex = index; - } + if (!SnappingManager.IsDragging) this._hoveredNodeIndex = index; }; - /** - * Translates the hovered node to the center of the screen - * @param index - * @returns - */ - translateHover = (index: number) => (this._hoveredNodeIndex === index && !DocumentView.SelectedDocs().includes(this.childDocs[index]) ? -50 : 0); - - isSelected = (index: number) => DocumentView.SelectedDocs().includes(this.childDocs[index]); - - /** - * Returns all the documents except the one that's currently selected - */ - inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d)); + isSelected = (doc: Doc) => this._docRefs.get(doc)?.IsSelected; childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2); childPanelHeight = () => this._props.PanelHeight() * this.fitContentScale; onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); - isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); - isChildContentActive = () => !!this.isContentActive(); + isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this.isAnyChildContentActive(); + isAnyChildContentActive = this._props.isAnyChildContentActive; /** * Returns the degree to rotate a card dependind on the amount of cards in their row and their index in said row @@ -202,13 +182,14 @@ export class CollectionCardView extends CollectionSubView() { if (amCards == 1) return 0; const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2)); - const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2))); - - if (amCards % 2 === 0 && possRotate === 0) { - return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2))); - } - if (amCards % 2 === 0 && index > (amCards + 1) / 2) { - return possRotate + stepMag; + if (amCards % 2 === 0) { + if (possRotate === 0) { + return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2))); + } + if (index > (amCards + 1) / 2) { + const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2))); + return possRotate + stepMag; + } } return possRotate; @@ -274,21 +255,12 @@ export class CollectionCardView extends CollectionSubView() { /** * Checks to see if a card is being dragged and calls the appropriate methods if so - * @param e the current pointer event */ - @action onPointerMove = (x: number, y: number) => { this._docDraggedIndex = DragManager.docsBeingDragged.length ? this.findCardDropIndex(x, y) : -1; }; - /** - * Handles external drop of images/PDFs etc from outside Dash. - */ - onExternalDrop = async (e: React.DragEvent): Promise => { - super.onExternalDrop(e, {}); - }; - /** * Resets all the doc dragging vairables once a card is dropped * @param e @@ -326,7 +298,7 @@ export class CollectionCardView extends CollectionSubView() { } /** - * Used to determine how to sort cards based on tags. The lestmost tags are given lower values while cards to the right are + * Used to determine how to sort cards based on tags. The leftmost tags are given lower values while cards to the right are * given higher values. Decimals are used to determine placement for cards with multiple tags * @param doc the doc whose value is being determined * @returns its value based on its tags @@ -352,24 +324,14 @@ export class CollectionCardView extends CollectionSubView() { docs.sort((docA, docB) => { const [typeA, typeB] = (() => { switch (sortType) { - case cardSortings.Time: - return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; - case cardSortings.Color: { - const d1 = DashColor(StrCast(docA.backgroundColor)); - const d2 = DashColor(StrCast(docB.backgroundColor)); - return [d1.hsv().hue(), d2.hsv().hue()]; - } - case cardSortings.Tag: - return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999]; - case cardSortings.Chat: - return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999]; - default: - return [StrCast(docA.type), StrCast(docB.type)]; + default: + case cardSortings.Type: return [StrCast(docA.type), StrCast(docB.type)]; + case cardSortings.Chat: return [NumCast(docA.chatIndex, 9999), NumCast(docB.chatIndex,9999)]; + case cardSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; + case cardSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()]; } - })(); - - const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0; - return isDesc ? out : -out; + })(); //prettier-ignore + return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1); }); if (dragIndex !== -1) { const draggedDoc = DragManager.docsBeingDragged[0]; @@ -382,6 +344,15 @@ export class CollectionCardView extends CollectionSubView() { return docs; }; + isChildContentActive = () => + this._props.isContentActive?.() === false + ? false + : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) + ? true + : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false + ? false + : undefined; + displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => ( ); @@ -416,13 +388,11 @@ export class CollectionCardView extends CollectionSubView() { if (this.sortedDocs.length < this._maxRowCount) { return this.sortedDocs.length; } - // 13 - 3 = 10 const totalCards = this.sortedDocs.length; // if 9 or less if (index < totalCards - (totalCards % this._maxRowCount)) { return this._maxRowCount; } - // (3) return totalCards % this._maxRowCount; }; /** @@ -443,17 +413,17 @@ export class CollectionCardView extends CollectionSubView() { /** * Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected * @param isHovered - * @param isSelected + * @param isActive * @param realIndex * @param amCards * @param calcRowIndex * @returns */ - calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { + calculateTranslateY = (isHovered: boolean, isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { const rowHeight = (this._props.PanelHeight() * this.fitContentScale) / this.numRows; const rowIndex = Math.trunc(realIndex / this._maxRowCount); const rowToCenterShift = this.numRows / 2 - rowIndex; - if (isSelected) return rowToCenterShift * rowHeight - rowHeight / 2; + if (isActive) return rowToCenterShift * rowHeight - rowHeight / 2; if (amCards == 1) return 50 * this.fitContentScale; return this.translateY(amCards, calcRowIndex, realIndex); }; @@ -576,15 +546,43 @@ export class CollectionCardView extends CollectionSubView() { await this.childPairStringListAndUpdateSortDesc(); }; + childScreenToLocal = computedFn((doc: Doc, index: number, calcRowIndex: number, isSelected: boolean, amCards: number) => () => { + // need to explicitly trigger an invalidation since we're reading everything from the Dom + this._forceChildXf; + this._props.ScreenToLocalTransform(); + + const dref = this._docRefs.get(doc); + const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv); + if (!scale) return new Transform(0, 0, 0); + + return new Transform(-translateX + (dref?.centeringX || 0) * scale, + -translateY + (dref?.centeringY || 0) * scale, 1) + .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore + }); + + cardPointerUp = action((doc: Doc) => { + // if a card doc has just moved, or a card is selected and in front, then ignore this event + if (this.isSelected(doc) || this._dropped) { + this._dropped = false; + } else { + // otherwise, turn off documentDecorations becase we're in a selection transition and want to avoid artifacts. + // Turn them back on when the animation has completed and the render and backend structures are in synch + SnappingManager.SetIsResizing(doc[Id]); + setTimeout( + action(() => { + SnappingManager.SetIsResizing(undefined); + this._forceChildXf++; + }), + 1000 + ); + } + }); + /** * Actually renders all the cards */ @computed get renderCards() { - const sortedDocs = this.sortedDocs; - const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc)); - const isEmpty = this.childDocsWithoutLinks.length === 0; - - if (isEmpty) { + if (!this.childDocsWithoutLinks.length) { return ( Sorry ! There are no cards in this group @@ -593,31 +591,18 @@ export class CollectionCardView extends CollectionSubView() { } // Map sorted documents to their rendered components - return sortedDocs.map((doc, index) => { - const realIndex = sortedDocs.indexOf(doc); + return this.sortedDocs.map((doc, index) => { + const realIndex = this.sortedDocs.indexOf(doc); const calcRowIndex = this.overflowIndexCalc(realIndex); const amCards = this.overflowAmCardsCalc(realIndex); const view = DocumentView.getDocumentView(doc, this.DocumentView?.()); - const isSelected = view?.ComponentView?.isAnyChildContentActive?.() || view?.IsSelected ? true : false; - const childScreenToLocal = () => { - // need to explicitly trigger an invalidation since we're reading everything from the Dom - this._forceChildXf; - this._props.ScreenToLocalTransform(); - - const dref = this._docRefs.get(doc); - const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv); - if (!scale) return new Transform(0, 0, 0); - - return new Transform(-translateX + (dref?.centeringX || 0) * scale, - -translateY + (dref?.centeringY || 0) * scale, 1) - .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore - }; + const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, !!view?.IsContentActive, amCards); const translateIfSelected = () => { const indexInRow = index % this._maxRowCount; const rowIndex = Math.trunc(index / this._maxRowCount); - const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2; + const rowCenterIndex = Math.min(this._maxRowCount, this.sortedDocs.length - rowIndex * this._maxRowCount) / 2; return (rowCenterIndex - indexInRow) * 100 - 50; }; const aspect = NumCast(doc.height) / NumCast(doc.width, 1); @@ -627,33 +612,18 @@ export class CollectionCardView extends CollectionSubView() { return (
{ - // if a card doc has just moved, or a card is selected and in front, then ignore this event - if (DocumentView.SelectedDocs().includes(doc) || this._dropped) { - this._dropped = false; - } else { - // otherwise, turn off documentDecorations becase we're in a selection transition and want to avoid artifacts. - // Turn them back on when the animation has completed and the render and backend structures are in synch - SnappingManager.SetIsResizing(doc[Id]); - setTimeout( - action(() => { - SnappingManager.SetIsResizing(undefined); - this._forceChildXf++; - }), - 1000 - ); - } - })} + className={`card-item${view?.IsContentActive ? '-active' : this.isAnyChildContentActive() ? '-inactive' : ''}`} + onPointerUp={() => this.cardPointerUp(doc)} style={{ width: this.childPanelWidth(), height: 'max-content', - transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px) - translateX(calc(${isSelected ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px)) - rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg) - scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`, + transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, !!view?.IsContentActive, realIndex, amCards, calcRowIndex)}px) + translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px)) + rotate(${!view?.IsContentActive ? this.rotate(amCards, calcRowIndex) : 0}deg) + scale(${view?.IsContentActive ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`, }} // prettier-ignore - onPointerEnter={() => !SnappingManager.IsDragging && this.setHoveredNodeIndex(index)}> + onPointerEnter={() => this.setHoveredNodeIndex(index)} + onPointerLeave={() => this.setHoveredNodeIndex(-1)}> {this.displayDoc(doc, childScreenToLocal)}
); @@ -680,8 +650,7 @@ export class CollectionCardView extends CollectionSubView() { ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }), ...(!isEmpty && { height: `${100 * this.fitContentScale}%` }), gridAutoRows: `${100 / this.numRows}%`, - }} - onMouseLeave={() => this.setHoveredNodeIndex(-1)}> + }}> {this.renderCards} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5d32482c3..581201a20 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -296,7 +296,7 @@ export function CollectionSubView() { return false; } - protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) { + protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions = {}, completed?: (docs: Doc[]) => void) { if (e.ctrlKey) { e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl return; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index c9e934448..7418d4360 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -16,7 +15,6 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView } from '../nodes/FieldView'; import { OpenWhere } from '../nodes/OpenWhere'; -import { CollectionCalendarView } from './CollectionCalendarView'; import { CollectionCardView } from './CollectionCardDeckView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d730e661b..81b5f946a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1206,6 +1206,7 @@ export class DocumentView extends DocComponent() { public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore public get IsSelected() { return this._selected; } // prettier-ignore + public get IsContentActive(){ return this._docViewInternal?.isContentActive(); } // prettier-ignore public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore public get ComponentView() { return this._docViewInternal?._componentView; } // prettier-ignore diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 683edba16..f6eb4009c 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -6,7 +6,6 @@ import { DateField } from '../../../fields/DateField'; import { Doc, Field, FieldType, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { ScriptField } from '../../../fields/ScriptField'; -import { WebField } from '../../../fields/URLField'; import { dropActionType } from '../../util/DropActionTypes'; import { Transform } from '../../util/Transform'; import { PinProps } from '../PinFuncs'; @@ -14,9 +13,9 @@ import { ViewBoxInterface } from '../ViewBoxInterface'; import { DocumentView } from './DocumentView'; import { FocusViewOptions } from './FocusViewOptions'; import { OpenWhere } from './OpenWhere'; +import { WebField } from '../../../fields/URLField'; export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt; -// eslint-disable-next-line no-use-before-define export type StyleProviderFuncType = ( doc: Opt, // eslint-disable-next-line no-use-before-define diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 678b7dd0b..d38cb5423 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -1,4 +1,4 @@ -import { Calendar, DateInput, EventClickArg, EventSourceInput } from '@fullcalendar/core'; +import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; import multiMonthPlugin from '@fullcalendar/multimonth'; import timeGrid from '@fullcalendar/timegrid'; -- cgit v1.2.3-70-g09d2 From cd7e213e7e0b9b6606ae068c29412bb64cdf6f2d Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 7 Oct 2024 10:43:48 -0400 Subject: comment cleanup --- .../views/collections/CollectionCardDeckView.tsx | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index a9ab9de26..0c2bcc580 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -411,15 +411,14 @@ export class CollectionCardView extends CollectionSubView() { translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (this._maxRowCount - calcRowCards) * (this.childPanelWidth() / 2)); /** - * Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected - * @param isHovered - * @param isActive - * @param realIndex - * @param amCards - * @param calcRowIndex - * @returns + * Determines how far to translate a card in the y direction depending on its index and if it's selected + * @param isActive whether the card is focused for interaction + * @param realIndex index of card from start of deck + * @param amCards ?? + * @param calcRowIndex index of card from start of row + * @returns Y translation of card */ - calculateTranslateY = (isHovered: boolean, isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { + calculateTranslateY = (isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { const rowHeight = (this._props.PanelHeight() * this.fitContentScale) / this.numRows; const rowIndex = Math.trunc(realIndex / this._maxRowCount); const rowToCenterShift = this.numRows / 2 - rowIndex; @@ -592,9 +591,8 @@ export class CollectionCardView extends CollectionSubView() { // Map sorted documents to their rendered components return this.sortedDocs.map((doc, index) => { - const realIndex = this.sortedDocs.indexOf(doc); - const calcRowIndex = this.overflowIndexCalc(realIndex); - const amCards = this.overflowAmCardsCalc(realIndex); + const calcRowIndex = this.overflowIndexCalc(index); + const amCards = this.overflowAmCardsCalc(index); const view = DocumentView.getDocumentView(doc, this.DocumentView?.()); const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, !!view?.IsContentActive, amCards); @@ -617,8 +615,8 @@ export class CollectionCardView extends CollectionSubView() { style={{ width: this.childPanelWidth(), height: 'max-content', - transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, !!view?.IsContentActive, realIndex, amCards, calcRowIndex)}px) - translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px)) + transform: `translateY(${this.calculateTranslateY(!!view?.IsContentActive, index, amCards, calcRowIndex)}px) + translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(index, amCards)}px)) rotate(${!view?.IsContentActive ? this.rotate(amCards, calcRowIndex) : 0}deg) scale(${view?.IsContentActive ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`, }} // prettier-ignore -- cgit v1.2.3-70-g09d2 From 34fdaf6f1405c59dbf18d03cc489a50715a7e4e9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 7 Oct 2024 13:02:15 -0400 Subject: lint fixes --- src/client/util/UndoManager.ts | 5 ++- src/client/views/GestureOverlay.tsx | 3 +- src/client/views/ScriptingRepl.tsx | 3 +- .../views/collections/CollectionDockingView.tsx | 8 ++--- .../CollectionFreeFormInfoState.tsx | 1 - src/client/views/nodes/DataVizBox/DataVizBox.tsx | 1 - src/fields/Doc.ts | 5 ++- src/fields/RichTextUtils.ts | 6 ---- src/server/websocket.ts | 42 +++++++++++----------- 9 files changed, 31 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index ce0e7768b..07d3bb708 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -43,7 +43,6 @@ export function undoable(fn: (...args: any[]) => T, batchName: string): (...a return function (...fargs) { const batch = UndoManager.StartBatch(batchName); try { - // eslint-disable-next-line prefer-rest-params return fn.apply(undefined, fargs); } finally { batch.end(); @@ -51,9 +50,9 @@ export function undoable(fn: (...args: any[]) => T, batchName: string): (...a }; } -// eslint-disable-next-line no-redeclare, @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor): any; -// eslint-disable-next-line no-redeclare, @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<(...args: any[]) => unknown>): any { if (!key) { return function (...fargs: unknown[]) { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 5fddaec9a..afeecaa63 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -70,7 +70,6 @@ export class GestureOverlay extends ObservableReactComponent ({ X: pt.x, Y: pt.y }))); if (intersectRect(scribbleBounds, strokeBounds)) { diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx index 2de867746..8ab91a6b5 100644 --- a/src/client/views/ScriptingRepl.tsx +++ b/src/client/views/ScriptingRepl.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/no-array-index-key */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; @@ -182,7 +181,7 @@ export class ScriptingRepl extends ObservableReactComponent { this.maybeScrollToBottom(); return; } - const result = undoable(() => script.run({}, e => this.commands.push({ command: this.commandString, result: e as string })), 'run:' + this.commandString)(); + const result = undoable(() => script.run({}, err => this.commands.push({ command: this.commandString, result: err as string })), 'run:' + this.commandString)(); if (result.success) { this.commands.push({ command: this.commandString, result: result.result }); this.commandsHistory.push(this.commandString); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index d1304b8f4..e1786d2c9 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -31,17 +31,17 @@ import { ScriptingRepl } from '../ScriptingRepl'; import { UndoStack } from '../UndoStack'; import './CollectionDockingView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { TabHTMLElement } from './TabDocView'; +import { TabDocView, TabHTMLElement } from './TabDocView'; @observer export class CollectionDockingView extends CollectionSubView() { - static tabClass: unknown = null; + static tabClass?: typeof TabDocView; /** * Initialize by assigning the add split method to DocumentView and by * configuring golden layout to render its documents using the specified React component * @param ele - typically would be set to TabDocView */ - public static Init(ele: unknown) { + public static Init(ele: typeof TabDocView) { this.tabClass = ele; DocumentView.addSplit = CollectionDockingView.AddSplit; } @@ -544,7 +544,7 @@ export class CollectionDockingView extends CollectionSubView() { tabCreated = (tab: { contentItem: { element: HTMLElement[] } }) => { this.tabMap.add(tab); // InitTab is added to the tab's HTMLElement in TabDocView - const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as unknown as TabHTMLElement; + const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as TabHTMLElement; tabdocviewContent?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index c17371151..51add85a8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -46,7 +46,6 @@ export function InfoState( gif?: string, entryFunc?: () => unknown ) { - // eslint-disable-next-line new-cap return new infoState(msg, arcs, gif, entryFunc); } diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index df6e74d85..3dd568fda 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox } from '@mui/material'; import { Colors, Toggle, ToggleType, Type } from 'browndash-components'; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index b3a17de8f..60cf8b321 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -97,9 +97,8 @@ export namespace Field { }); return script; } - export function toString(fieldIn: unknown) { - const field = fieldIn as FieldType; - if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field); + export function toString(field: FieldResult | FieldType | undefined) { + if (field instanceof Promise || typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field); return field?.[ToString]?.() || ''; } export function IsField(field: unknown): field is FieldType; diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index b3534dde7..8c073c87b 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-namespace */ /* eslint-disable no-await-in-loop */ /* eslint-disable no-use-before-define */ import { AssertionError } from 'assert'; @@ -175,7 +174,6 @@ export namespace RichTextUtils { const indentMap = new Map(); let globalOffset = 0; const nodes: Node[] = []; - // eslint-disable-next-line no-restricted-syntax for (const element of structured) { if (Array.isArray(element)) { lists.push(element); @@ -374,11 +372,9 @@ export namespace RichTextUtils { const marksToStyle = async (nodes: (Node | null)[]): Promise => { const requests: docsV1.Schema$Request[] = []; let position = 1; - // eslint-disable-next-line no-restricted-syntax for (const node of nodes) { if (node === null) { position += 2; - // eslint-disable-next-line no-continue continue; } const { marks, attrs, nodeSize } = node; @@ -390,9 +386,7 @@ export namespace RichTextUtils { }; let mark: Mark; const markMap = BuildMarkMap(marks); - // eslint-disable-next-line no-restricted-syntax for (const markName of Object.keys(schema.marks)) { - // eslint-disable-next-line no-cond-assign if (ignored.includes(markName) || !(mark = markMap[markName])) { continue; } diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 1e25a8a27..effe94219 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -61,27 +61,6 @@ export namespace WebSocket { Database.Instance.getDocuments(ids, callback); } - const pendingOps = new Map(); - - function dispatchNextOp(id: string): unknown { - const next = pendingOps.get(id)?.shift(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const nextOp = (res: boolean) => dispatchNextOp(id); - if (next) { - const { diff, socket } = next; - // ideally, we'd call the Database update method for all actions, but for now we handle list insertion/removal on our own - switch (diff.diff.$addToSet ? 'add' : diff.diff.$remFromSet ? 'rem' : 'set') { - case 'add': return GetRefFieldLocal(id, (result) => addToListField(socket, diff, result, nextOp)); // prettier-ignore - case 'rem': return GetRefFieldLocal(id, (result) => remFromListField(socket, diff, result, nextOp)); // prettier-ignore - default: return Database.Instance.update(id, diff.diff, - () => nextOp(socket.broadcast.emit(MessageStore.UpdateField.Message, diff)), - false - ); // prettier-ignore - } - } - return !pendingOps.get(id)?.length && pendingOps.delete(id); - } - function addToListField(socket: Socket, diff: Diff, listDoc: serializedDoctype | undefined, cb: (res: boolean) => void): void { const $addToSet = diff.diff.$addToSet as serializedFieldsType; const updatefield = Array.from(Object.keys($addToSet ?? {}))[0]; @@ -181,6 +160,27 @@ export namespace WebSocket { } else cb(false); } + const pendingOps = new Map(); + + function dispatchNextOp(id: string): unknown { + const next = pendingOps.get(id)?.shift(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const nextOp = (res: boolean) => dispatchNextOp(id); + if (next) { + const { diff, socket } = next; + // ideally, we'd call the Database update method for all actions, but for now we handle list insertion/removal on our own + switch (diff.diff.$addToSet ? 'add' : diff.diff.$remFromSet ? 'rem' : 'set') { + case 'add': return GetRefFieldLocal(id, (result) => addToListField(socket, diff, result, nextOp)); // prettier-ignore + case 'rem': return GetRefFieldLocal(id, (result) => remFromListField(socket, diff, result, nextOp)); // prettier-ignore + default: return Database.Instance.update(id, diff.diff, + () => nextOp(socket.broadcast.emit(MessageStore.UpdateField.Message, diff)), + false + ); // prettier-ignore + } + } + return !pendingOps.get(id)?.length && pendingOps.delete(id); + } + function UpdateField(socket: Socket, diff: Diff) { const curUser = socketMap.get(socket); if (curUser) { -- cgit v1.2.3-70-g09d2 From 3137f422a004adef82124989280b40852a033afe Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 7 Oct 2024 13:38:54 -0400 Subject: fixed card 'pile' function. lint fixes. --- src/client/views/global/globalScripts.ts | 27 +++++----------------- .../views/nodes/RecordingBox/ProgressBar.tsx | 2 -- src/fields/Doc.ts | 7 +----- 3 files changed, 7 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 423a2d6ef..2b8908899 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -4,6 +4,7 @@ import { runInAction } from 'mobx'; import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; +import { List } from '../../../fields/List'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { Gestures } from '../../../pen-gestures/GestureTypes'; @@ -16,7 +17,6 @@ import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; import { InkTranscription } from '../InkTranscription'; import { InkingStroke } from '../InkingStroke'; -import { MainView } from '../MainView'; import { PropertiesView } from '../PropertiesView'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; @@ -40,6 +40,7 @@ import { VideoBox } from '../nodes/VideoBox'; import { WebBox } from '../nodes/WebBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; +import { OpenWhere } from '../nodes/OpenWhere'; // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function IsNoneSelected() { @@ -240,31 +241,15 @@ ScriptingGlobals.add(function showFreeform( ['pile', { checkResult: (doc: Doc) => doc._type_collection == CollectionViewType.Freeform, setDoc: (doc: Doc, dv: DocumentView) => { - doc._type_collection = CollectionViewType.Freeform; const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), { + title: doc.title + "_carousel", _width: 250, _height: 200, _layout_fitWidth: false, _layout_autoHeight: true, + childFilters: new List(StrListCast(doc.childFilters)) }); - - - const iconMap: { [key: number]: string } = { - 0: 'star', - 1: 'heart', - 2: 'cloud', - 3: 'bolt' - }; - - for (let i=0; i<4; i++){ - if (isAttrFiltered(iconMap[i])){ - newCol[iconMap[i]] = true - } - } - - newCol && dv.ComponentView?.addDocument?.(newCol); - DocumentView.showDocument(newCol, { willZoomCentered: true }) - + dv._props.addDocTab?.(newCol, OpenWhere.addRight); }, }], ]); @@ -300,7 +285,7 @@ ScriptingGlobals.add(function setTagFilter(tag: string, added: boolean, checkRes added ? Doc.setDocFilter(selected, 'tags', tag, 'check') : Doc.setDocFilter(selected, 'tags', tag, 'remove'); } else { SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0); - SnappingManager.SetPropertiesWidth(MainView.Instance.propertiesWidth() < 15 ? 250 : 0); + SnappingManager.SetPropertiesWidth(SnappingManager.PropertiesWidth < 15 ? 250 : 0); PropertiesView.Instance?.CloseAll(); runInAction(() => (PropertiesView.Instance.openFilters = SnappingManager.PropertiesWidth > 5)); } diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx index 62798bc2f..7e91df7ab 100644 --- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx +++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx @@ -1,5 +1,3 @@ -/* eslint-disable react/no-array-index-key */ -/* eslint-disable react/require-default-props */ import * as React from 'react'; import { useEffect, useState, useRef } from 'react'; import './ProgressBar.scss'; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 60cf8b321..4d256e8f2 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -110,7 +110,7 @@ export namespace Field { export function Copy(field: unknown) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : (field as FieldType); } - UndoManager.SetFieldPrinter(toString); + UndoManager.SetFieldPrinter((val: unknown) => (IsField(val) ? toString(val) : '')); } export type FieldType = number | string | boolean | ObjectField | RefField; export type Opt = T | undefined; @@ -336,7 +336,6 @@ export class Doc extends RefField { if (!id || forceSave) { DocServer.CreateDocField(docProxy); } - // eslint-disable-next-line no-constructor-return return docProxy; // need to return the proxy from the constructor so that all our added fields will get called } @@ -463,8 +462,6 @@ export class Doc extends RefField { }); } } - -// eslint-disable-next-line no-redeclare export namespace Doc { export let SelectOnLoad: Doc | undefined; export function SetSelectOnLoad(doc: Doc | undefined) { @@ -660,7 +657,6 @@ export namespace Doc { if (reversed) list.splice(0, 0, doc); else list.push(doc); } else { - // eslint-disable-next-line no-lonely-if if (reversed) list.splice(before ? list.length - ind + 1 : list.length - ind, 0, doc); else list.splice(before ? ind : ind + 1, 0, doc); } @@ -1192,7 +1188,6 @@ export namespace Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); } export function SetUserDoc(doc: Doc) { - // eslint-disable-next-line no-return-assign return (manager._user_doc = doc); } -- cgit v1.2.3-70-g09d2 From 827dd6aba9eefcd3beddbc246134cd6d6492fcf8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 7 Oct 2024 13:49:08 -0400 Subject: fixed import cycle by making addHotKey a prop --- src/client/views/FilterPanel.tsx | 36 ++++-------------------------------- src/client/views/MainView.tsx | 35 ++++++++++++++++++++++++++++++++--- src/client/views/PropertiesView.tsx | 3 ++- 3 files changed, 38 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index e34b66963..11425e477 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -1,3 +1,4 @@ +import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable, ObservableMap } from 'mobx'; @@ -12,18 +13,15 @@ import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; -import { DocCast, StrCast } from '../../fields/Types'; -import { Button, CurrentUserUtils } from '../util/CurrentUserUtils'; +import { StrCast } from '../../fields/Types'; import { SearchUtil } from '../util/SearchUtil'; import { SnappingManager } from '../util/SnappingManager'; import { undoable } from '../util/UndoManager'; import { FieldsDropdown } from './FieldsDropdown'; import './FilterPanel.scss'; import { DocumentView } from './nodes/DocumentView'; -import { ButtonType } from './nodes/FontIconBox/FontIconBox'; import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { IconProp } from '@fortawesome/fontawesome-svg-core'; interface HotKeyButtonProps { hotKey: Doc; @@ -159,6 +157,7 @@ const HotKeyIconButton: React.FC = observer(({ hotKey /*, sel interface filterProps { Document: Doc; + addHotKey: (hotKey: string) => void; } @observer @@ -356,33 +355,6 @@ export class FilterPanel extends ObservableReactComponent { return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; - /** - * Allows users to add a filter hotkey to the properties panel. Will also update the multitoggle at the top menu and the - * icontags tht are displayed on the documents themselves - * @param hotKey tite of the new hotkey - */ - addHotkey = (hotKey: string) => { - const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); - const filter = DocCast(buttons.Filter); - const title = hotKey.startsWith('#') ? hotKey.substring(1) : hotKey; - - const newKey: Button = { - title, - icon: 'question', - toolTip: `Click to toggle the ${title}'s group's visibility`, - btnType: ButtonType.ToggleButton, - expertMode: false, - toolType: '#' + title, - funcs: {}, - scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' }, - }; - - const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter); - newBtn.isSystem = newBtn[DocData].isSystem = undefined; - - Doc.AddToFilterHotKeys(newBtn); - }; - /** * Renders the newly formed hotkey icon buttons * @returns the buttons to be rendered @@ -472,7 +444,7 @@ export class FilterPanel extends ObservableReactComponent {
- +
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 31d7e82a6..c61cdea54 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -20,6 +20,7 @@ import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; import { CalendarManager } from '../util/CalendarManager'; import { CaptureManager } from '../util/CaptureManager'; +import { Button, CurrentUserUtils } from '../util/CurrentUserUtils'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; @@ -41,6 +42,7 @@ import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import { GestureOverlay } from './GestureOverlay'; +import { InkTranscription } from './InkTranscription'; import { LightboxView } from './LightboxView'; import './MainView.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -60,6 +62,7 @@ import { LinkMenu } from './linking/LinkMenu'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; +import { ButtonType } from './nodes/FontIconBox/FontIconBox'; import { ImageEditorData as ImageEditor } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; @@ -73,9 +76,8 @@ import GenerativeFill from './nodes/generativeFill/GenerativeFill'; import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; -import { TopBar } from './topbar/TopBar'; import { SmartDrawHandler } from './smartdraw/SmartDrawHandler'; -import { InkTranscription } from './InkTranscription'; +import { TopBar } from './topbar/TopBar'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore @@ -850,6 +852,33 @@ export class MainView extends ObservableReactComponent { return true; }; + /** + * Allows users to add a filter hotkey to the properties panel. Will also update the multitoggle at the top menu and the + * icontags tht are displayed on the documents themselves + * @param hotKey tite of the new hotkey + */ + addHotKey = (hotKey: string) => { + const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); + const filter = DocCast(buttons.Filter); + const title = hotKey.startsWith('#') ? hotKey.substring(1) : hotKey; + + const newKey: Button = { + title, + icon: 'question', + toolTip: `Click to toggle the ${title}'s group's visibility`, + btnType: ButtonType.ToggleButton, + expertMode: false, + toolType: '#' + title, + funcs: {}, + scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' }, + }; + + const newBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter); + newBtn.isSystem = newBtn[DocData].isSystem = undefined; + + Doc.AddToFilterHotKeys(newBtn); + }; + @computed get mainInnerContent() { const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth(); const width = this.propertiesWidth() + leftMenuFlyoutWidth; @@ -878,7 +907,7 @@ export class MainView extends ObservableReactComponent { )}
- +
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index d0c47875f..715f079d8 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -50,6 +50,7 @@ interface PropertiesViewProps { height: number; styleProvider?: StyleProviderFuncType; addDocTab: (doc: Doc, where: OpenWhere) => boolean; + addHotKey: (hotKey: string) => void; } @observer @@ -1277,7 +1278,7 @@ export class PropertiesView extends ObservableReactComponent { this.openFilters = bool; })} onDoubleClick={this.CloseAll}>
- +
); // prettier-ignore -- cgit v1.2.3-70-g09d2 From 09b722789afa5d7c5edfc701978f17e4745113bc Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 7 Oct 2024 14:25:35 -0400 Subject: more lint --- src/client/documents/DocUtils.ts | 2 -- src/client/views/TagsView.tsx | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 0699ea09f..1130a9ae8 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -1,5 +1,3 @@ -/* eslint-disable prefer-destructuring */ -/* eslint-disable default-param-last */ /* eslint-disable no-use-before-define */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { saveAs } from 'file-saver'; diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 9858e7b61..072cae3af 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -84,9 +84,7 @@ export class TagItem extends ObservableReactComponent { */ public static allDocsWithTag = (tag: string) => DocListCast(TagItem.findTagCollectionDoc(tag)?.[DocData].docs); - public static docHasTag = (doc: Doc, tag: string) => { - return StrListCast(doc?.tags).includes(tag); - }; + public static docHasTag = (doc: Doc, tag: string) => StrListCast(doc?.tags).includes(tag); /** * Adds a tag to the metadata of this document and adds the Doc to the corresponding tag collection Doc (or creates it) * @param tag tag string -- cgit v1.2.3-70-g09d2 From 11244a698bce594982ee5dca55b9695bb774451c Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 7 Oct 2024 18:13:38 -0400 Subject: moved all quiz code out of LabelBox and ImageBox and into StyleProviderQuiz. changed quizBoxes and quizMode to be stored as Doc metadata. Extended styles to cover contextMenuItems. remove this.setListening() from comparisonBox until contextMenu selectedVal is fixed. --- src/client/views/StyleProp.ts | 2 + src/client/views/StyleProvider.tsx | 10 +- src/client/views/StyleProviderQuiz.scss | 40 ++++ src/client/views/StyleProviderQuiz.tsx | 391 +++++++++++++++++++++++++++++++ src/client/views/nodes/ComparisonBox.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 3 + src/client/views/nodes/FieldView.tsx | 2 + src/client/views/nodes/ImageBox.scss | 41 ---- src/client/views/nodes/ImageBox.tsx | 365 ++--------------------------- src/client/views/nodes/LabelBox.tsx | 52 +--- 10 files changed, 466 insertions(+), 441 deletions(-) create mode 100644 src/client/views/StyleProviderQuiz.scss create mode 100644 src/client/views/StyleProviderQuiz.tsx (limited to 'src') diff --git a/src/client/views/StyleProp.ts b/src/client/views/StyleProp.ts index dd5b98cfe..44d3bf757 100644 --- a/src/client/views/StyleProp.ts +++ b/src/client/views/StyleProp.ts @@ -21,4 +21,6 @@ export enum StyleProp { FontFamily = 'fontFamily', // font family of text FontWeight = 'fontWeight', // font weight of text Highlighting = 'highlighting', // border highlighting + ContextMenuItems = 'contextMenuItems', // menu items to add to context menu + AnchorMenuItems = 'anchorMenuItems', } diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 16f6aa40b..44bea57eb 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -21,6 +21,7 @@ import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; +import { styleProviderQuiz } from './StyleProviderQuiz'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; import { TagsView } from './TagsView'; @@ -89,6 +90,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt styleProvider?.(doc, props, StyleProp.Color) as string; const opacity = () => styleProvider?.(doc, props, StyleProp.Opacity); const layoutShowTitle = () => styleProvider?.(doc, props, StyleProp.ShowTitle) as string; + + // bcz: For now, this is how to add custom-stylings (like a Quiz styling) for app-specific purposes. The quiz styling will short-circuit + // the regular stylings for items that it controls (eg., things with a quiz field, or images) + const quizProp = styleProviderQuiz.quizStyleProvider(doc, props, property); + if (quizProp !== undefined) return quizProp; + // prettier-ignore switch (property.split(':')[0]) { case StyleProp.TreeViewIcon: { @@ -318,7 +326,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length ? 'orange' // 'inheritsFilter' : undefined; - return !showFilterIcon || props?.hideFilterStatus ? null : ( + return !showFilterIcon || hideFilterStatus ? null : (
{ + try { + 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; + } + } + /** + * Creates label boxes over text on the image to be filled in. + * @param boxes + * @param texts + */ + async function createBoxes(img: ImageBox, boxes: [[[number, number]]], texts: [string]) { + img.Document._quizBoxes = new List([]); + for (let i = 0; i < boxes.length; i++) { + const coords = boxes[i] ? boxes[i] : []; + const width = coords[1][0] - coords[0][0]; + const height = coords[2][1] - coords[0][1]; + const text = texts[i]; + + const newCol = Docs.Create.LabelDocument({ + _width: width, + _height: height, + _layout_fitWidth: true, + title: '', + }); + const scaling = 1 / (img._props.NativeDimScaling?.() || 1); + newCol.x = coords[0][0] + NumCast(img.marqueeref.current?.left) * scaling; + newCol.y = coords[0][1] + NumCast(img.marqueeref.current?.top) * scaling; + + newCol.zIndex = 1000; + newCol.forceActive = true; + newCol.quiz = text; + newCol[DocData].textTransform = 'none'; + Doc.AddDocToList(img.Document, '_quizBoxes', newCol); + img.addDocument(newCol); + // img._loading = false; + } + } + + /** + * Calls backend to find any text on an image. Gets the text and the + * coordinates of the text and creates label boxes at those locations. + * @param quiz + * @param i + */ + async function pushInfo(imgBox: ImageBox, quiz: quizMode, i?: string) { + imgBox.Document._quizMode = quiz; + const quizBoxes = DocListCast(imgBox.Document.quizBoxes); + if (!quizBoxes.length) { + // this._loading = true; + + const img = { + file: i ? i : imgBox.paths[0], + drag: i ? 'drag' : 'full', + smart: quiz, + }; + const response = await axios.post('http://localhost:105/labels/', img, { + headers: { + 'Content-Type': 'application/json', + }, + }); + if (response.data['boxes'].length != 0) { + createBoxes(imgBox, response.data['boxes'], response.data['text']); + } else { + // this._loading = false; + } + } else quizBoxes.forEach(box => (box.hidden = false)); + } + + async function createCanvas(img: ImageBox) { + const canvas = document.createElement('canvas'); + const scaling = 1 / (img._props.NativeDimScaling?.() || 1); + const w = AnchorMenu.Instance.marqueeWidth * scaling; + const h = AnchorMenu.Instance.marqueeHeight * scaling; + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); // draw image to canvas. scale to target dimensions + if (ctx) { + img.imageRef && ctx.drawImage(img.imageRef, NumCast(img.marqueeref.current?.left) * scaling, NumCast(img.marqueeref.current?.top) * scaling, w, h, 0, 0, w, h); + } + const blob = await ImageUtility.canvasToBlob(canvas); + return selectUrlToBase64(blob); + } + /** + * Create flashcards from an image. + */ + async function getImageDesc(img: ImageBox) { + // this._loading = true; + try { + const hrefBase64 = await createCanvas(img); + const response = await gptImageLabel(hrefBase64, 'Make flashcards out of this image with each question and answer labeled as "question" and "answer". Do not label each flashcard and do not include asterisks: '); + AnchorMenu.Instance.transferToFlashcard(response, NumCast(img.layoutDoc['x']), NumCast(img.layoutDoc['y'])); + } catch (error) { + console.log('Error', error); + } + // this._loading = false; + } + + /** + * Calls the createCanvas and pushInfo methods to convert the + * image to a form that can be passed to GPT and find the locations + * of the text. + */ + async function makeLabels(img: ImageBox) { + try { + const hrefBase64 = await createCanvas(img); + pushInfo(img, quizMode.NORMAL, hrefBase64); + } catch (error) { + console.log('Error', error); + } + } + + /** + * Determines whether two words should be considered + * the same, allowing minor typos. + * @param str1 + * @param str2 + * @returns + */ + function levenshteinDistance(str1: string, str2: string) { + const len1 = str1.length; + const len2 = str2.length; + const dp = Array.from(Array(len1 + 1), () => Array(len2 + 1).fill(0)); + + if (len1 === 0) return len2; + if (len2 === 0) return len1; + + for (let i = 0; i <= len1; i++) dp[i][0] = i; + for (let j = 0; j <= len2; j++) dp[0][j] = j; + + for (let i = 1; i <= len1; i++) { + for (let j = 1; j <= len2; j++) { + const cost = str1[i - 1] === str2[j - 1] ? 0 : 1; + dp[i][j] = Math.min( + dp[i - 1][j] + 1, // deletion + dp[i][j - 1] + 1, // insertion + dp[i - 1][j - 1] + cost // substitution + ); + } + } + + return dp[len1][len2]; + } + + /** + * Different algorithm for determining string similarity. + * @param str1 + * @param str2 + * @returns + */ + function jaccardSimilarity(str1: string, str2: string) { + const set1 = new Set(str1.split(' ')); + const set2 = new Set(str2.split(' ')); + + const intersection = new Set([...set1].filter(x => set2.has(x))); + const union = new Set([...set1, ...set2]); + + return intersection.size / union.size; + } + + /** + * Averages the jaccardSimilarity and levenshteinDistance scores + * to determine string similarity for the labelboxes answers and + * the users response. + * @param str1 + * @param str2 + * @returns + */ + function stringSimilarity(str1: string, str2: string) { + const levenshteinDist = levenshteinDistance(str1, str2); + const levenshteinScore = 1 - levenshteinDist / Math.max(str1.length, str2.length); + + const jaccardScore = jaccardSimilarity(str1, str2); + + // Combine the scores with a higher weight on Jaccard similarity + return 0.5 * levenshteinScore + 0.5 * jaccardScore; + } + /** + * Returns whether two strings are similar + * @param input + * @param target + * @returns + */ + function compareWords(input: string, target: string) { + const distance = stringSimilarity(input.toLowerCase(), target.toLowerCase()); + return distance >= 0.7; + } + + /** + * GPT returns a hex color for what color the label box should be based on + * the correctness of the users answer. + * @param inputString + * @returns + */ + function extractHexAndSentences(inputString: string) { + // Regular expression to match a hexadecimal number at the beginning followed by a period and sentences + const regex = /^#([0-9A-Fa-f]+)\.\s*(.+)$/s; + const match = inputString.match(regex); + + if (match) { + const hexNumber = match[1]; + const sentences = match[2].trim(); + return { hexNumber, sentences }; + } else { + return { error: 'The input string does not match the expected format.' }; + } + } + /** + * Check whether the contents of the label boxes on an image are correct. + */ + function check(img: ImageBox) { + //this._loading = true; + img.quizBoxes.forEach(async doc => { + const input = StrCast(doc[DocData].title); + if (img.quizMode == quizMode.SMART && input) { + const questionText = 'Question: What was labeled in this image?'; + const rubricText = ' Rubric: ' + StrCast(doc.quiz); + const queryText = + questionText + + ' UserAnswer: ' + + input + + '. ' + + rubricText + + '. One sentence and evaluate based on meaning, not wording. Provide a hex color at the beginning with a period after it on a scale of green (minor details missed) to red (big error) for how correct the answer is. Example: "#FFFFFF. Pasta is delicious."'; + const response = await gptAPICall(queryText, GPTCallType.QUIZ); + const hexSent = extractHexAndSentences(response); + doc.quiz = hexSent.sentences?.replace(/UserAnswer/g, "user's answer").replace(/Rubric/g, 'rubric'); + doc.backgroundColor = '#' + hexSent.hexNumber; + } else { + const match = compareWords(input, StrCast(doc.quiz).trim()); + doc.backgroundColor = match ? '#11c249' : '#eb2d2d'; + } + }); + //this._loading = false; + } + + function redo(img: ImageBox) { + img.quizBoxes.forEach(doc => { + doc[DocData].title = ''; + doc.backgroundColor = '#e4e4e4'; + }); + } + + /** + * Get rid of all the label boxes on the images. + */ + function exitQuizMode(img: ImageBox) { + img.Document._quizMode = quizMode.NONE; + DocListCast(img.Document._quizBoxes).forEach(box => { + box.hidden = true; + }); + } + + export function quizStyleProvider(doc: Opt, props: Opt, property: string) { + const editLabelAnswer = (qdoc: Doc) => { + // when click the pencil, set the text to the quiz content. when click off, set the quiz text to that and set textbox to nothing. + if (!qdoc._editLabel) { + qdoc.title = StrCast(qdoc.quiz); + } else { + qdoc.quiz = StrCast(qdoc.title); + qdoc.title = ''; + } + qdoc._editLabel = !qdoc._editLabel; + }; + const editAnswer = (qdoc: Opt) => { + return ( + + {qdoc?._editLabel ? 'save' : 'edit correct answer'} +
+ }> +
setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => qdoc && editLabelAnswer(qdoc))}> + +
+ + ); + }; + const answerIcon = (qdoc: Opt) => { + return ( + + {StrCast(qdoc?.quiz ?? '')} + + }> +
+ + +
+
+ ); + }; + const checkIcon = (img: ImageBox) => ( + Check}> +
check(img)}> + +
+
+ ); + const redoIcon = (img: ImageBox) => ( + Redo}> +
redo(img)}> + +
+
+ ); + + const imgBox = props?.DocumentView?.().ComponentView as ImageBox; + switch (property) { + case StyleProp.Decorations: + { + if (doc?.quiz) { + // this should only be set on Labels that are part of an image quiz + return ( + <> + {editAnswer(doc?.[DocData])} + {answerIcon(doc)} + + ); + } else if (imgBox?.Document._quizMode && imgBox.Document._quizMode !== quizMode.NONE) { + return ( + <> + {checkIcon(imgBox)} + {redoIcon(imgBox)} + + ); + } + } + break; + case StyleProp.ContextMenuItems: + if (imgBox) { + const quizes: ContextMenuProps[] = []; + quizes.push({ + description: 'Smart Check', + event: doc?.quizMode == quizMode.NONE ? () => pushInfo(imgBox, quizMode.SMART) : () => exitQuizMode(imgBox), + icon: 'pen-to-square', + }); + quizes.push({ + description: 'Normal', + event: doc?.quizMode == quizMode.NONE ? () => pushInfo(imgBox, quizMode.NORMAL) : () => exitQuizMode(imgBox), + icon: 'pencil', + }); + ContextMenu.Instance?.addItem({ description: 'Quiz Mode', subitems: quizes, icon: 'file-pen' }); + } + break; + case StyleProp.AnchorMenuItems: + if (imgBox) { + AnchorMenu.Instance.gptFlashcards = () => getImageDesc(imgBox); + AnchorMenu.Instance.makeLabels = () => makeLabels(props?.DocumentView?.().ComponentView as ImageBox); + } + } + return undefined; + } +} diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 9fb8bc4d6..c32bbc803 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -44,7 +44,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() constructor(props: FieldViewProps) { super(props); makeObservable(this); - this.setListening(); } @observable private _inputValue = ''; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5054432a9..04a31fd83 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -545,6 +545,9 @@ export class DocumentViewInternal extends DocComponent ContextMenu.Instance.addItem(item)); + const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), []); StrListCast(this.Document.contextMenuLabels).forEach((label, i) => cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' }) diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ca783d034..170966471 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -14,6 +14,7 @@ import { DocumentView } from './DocumentView'; import { FocusViewOptions } from './FocusViewOptions'; import { OpenWhere } from './OpenWhere'; import { WebField } from '../../../fields/URLField'; +import { ContextMenuProps } from '../ContextMenuItem'; export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt; export type StyleProviderFuncType = ( @@ -23,6 +24,7 @@ export type StyleProviderFuncType = ( property: string ) => | Opt + | ContextMenuProps[] | { clipPath: string; jsx: JSX.Element } | JSX.Element | JSX.IntrinsicElements diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 4d199b360..3ffda5a35 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -139,44 +139,3 @@ .imageBox-fadeBlocker-hover { opacity: 0; } - -.loading-spinner { - position: absolute; - display: flex; - justify-content: center; - align-items: center; - height: 100%; - width: 100%; - // left: 50%; - // top: 50%; - z-index: 200; - font-size: 20px; - font-weight: bold; - color: #17175e; -} - -.check-icon { - position: absolute; - right: 40; - bottom: 10; - color: green; - display: inline-block; - font-size: 20px; - overflow: hidden; -} - -.redo-icon { - position: absolute; - right: 10; - bottom: 10; - color: black; - display: inline-block; - font-size: 20px; - overflow: hidden; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 31f6df2ea..0b474076b 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -17,13 +17,11 @@ import { Cast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Type import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; -import { gptAPICall, GPTCallType, gptImageLabel } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils, FollowLinkScript } from '../../documents/DocUtils'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; -import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { undoable, undoBatch } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; @@ -38,16 +36,8 @@ import { StyleProp } from '../StyleProp'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; -import { ImageUtility } from './generativeFill/generativeFillUtils/ImageHandler'; import './ImageBox.scss'; import { OpenWhere } from './OpenWhere'; -// import stringSimilarity from 'string-similarity'; - -enum quizMode { - SMART = 'smart', - NORMAL = 'normal', - NONE = 'none', -} export class ImageEditorData { // eslint-disable-next-line no-use-before-define @@ -79,25 +69,24 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } + _ffref = React.createRef(); private _ignoreScroll = false; private _forcedScroll = false; private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; private _getAnchor: (savedAnnotations: Opt>, addAsAnnotation: boolean) => Opt = () => undefined; private _overlayIconRef = React.createRef(); - private _marqueeref = React.createRef(); private _mainCont: React.RefObject = React.createRef(); private _annotationLayer: React.RefObject = React.createRef(); - private _imageRef: HTMLImageElement | null = null; //