From 4b231bbaf21939144ed8639d35f022834a406e59 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Oct 2024 22:02:19 -0400 Subject: changed layout_isFlashcard to layout_flashcardType with value of 'flashcard' to make it easy to search for flashcards. made carousel views able to focus() on a Doc. added ability for carousel and card views to have anchors so they can be pinned and linked. fixed pinning collections to save filters --- .../views/collections/CollectionCarousel3DView.tsx | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index a71cc43ba..2be3ef48f 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -6,7 +6,7 @@ import { returnZero } from '../../../ClientUtils'; import { Utils } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; @@ -16,6 +16,9 @@ import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { computedFn } from 'mobx-utils'; +import { Docs } from '../../documents/Documents'; +import { PinDocView } from '../PinFuncs'; +import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @@ -96,6 +99,33 @@ export class CollectionCarousel3DView extends CollectionSubView() { index !== -1 && (this.layoutDoc._carousel_index = index); return undefined; }; + getAnchor = (addAsAnnotation: boolean) => { + const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document }); + anchor.config_carousel_index = this.layoutDoc._carousel_index; + PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document); + if (addAsAnnotation) { + // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered + Doc.AddDocToList(this.dataDoc, this._props.fieldKey + '_annotations', anchor); + } + return anchor; + }; + // pinned / linked anchor doc includes selected rows, graph titles, and graph colors + restoreView = (viewData: Doc) => { + if (viewData.config_carousel_index !== undefined && this.layoutDoc._carousel_index !== viewData.config_carousel_index) { + this.layoutDoc._carousel_index = viewData.config_carousel_index; + return true; + } + return false; + }; + addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => { + const doc = toList(docsIn).lastElement(); + const where = location.split(':')[0]; + if (where === OpenWhere.lightbox && (this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc))) { + if (doc.hidden) doc.hidden = false; + if (!location.includes(OpenWhereMod.always)) return true; + } + return this._props.addDocTab(docsIn, location); + }; @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); -- cgit v1.2.3-70-g09d2 From 7b29d9c14cb1ea786e64655b33b245ed14bd2d9e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Oct 2024 22:20:45 -0400 Subject: from last --- src/client/views/collections/CollectionCarousel3DView.tsx | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 2be3ef48f..81e921724 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -33,6 +33,9 @@ export class CollectionCarousel3DView extends CollectionSubView() { makeObservable(this); } + componentDidMount(): void { + this._props.setContentViewBox?.(this); + } componentWillUnmount() { this._dropDisposer?.(); } -- cgit v1.2.3-70-g09d2 From 0728e918e6d075c0eda738b7d2fdbf6095c714ae Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 18 Oct 2024 00:28:55 -0400 Subject: fixed following links/show document of items in tabs/carousel/card view to set options.didMove correctly so that toggleTarget works. --- src/client/documents/Documents.ts | 2 ++ .../views/collections/CollectionCardDeckView.tsx | 42 +++++++++------------- .../views/collections/CollectionCarousel3DView.tsx | 40 +++++++-------------- .../views/collections/CollectionCarouselView.tsx | 33 ++++++----------- src/client/views/collections/CollectionSubView.tsx | 14 +++++++- src/client/views/collections/TabDocView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 19 ++++------ src/client/views/nodes/trails/PresBox.tsx | 3 +- src/extensions/ExtensionsTypings.ts | 8 +++++ src/extensions/Extensions_Array.ts | 10 ++++-- 10 files changed, 78 insertions(+), 94 deletions(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f2e85cf7a..be3b9c3bb 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -372,6 +372,8 @@ export class DocumentOptions { config_panX?: NUMt = new NumInfo('panX saved as a view spec', false); config_panY?: NUMt = new NumInfo('panY saved as a view spec', false); config_zoom?: NUMt = new NumInfo('zoom saved as a view spec', false); + config_carousel_index?: NUMt = new NumInfo('saved carousel index', false); + config_curDoc?: DOCt = new DocInfo('current doc in a collection view, e.g., cardView'); config_viewScale?: NUMt = new NumInfo('viewScale saved as a view Spec', false); presentation_transition?: NUMt = new NumInfo('the time taken for the transition TO a document', false); presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view', false); diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 60305244c..729a1585a 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -9,25 +9,24 @@ import { Animation, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, DateCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; +import { BoolCast, DateCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types'; import { URLField } from '../../../fields/URLField'; import { gptImageLabel } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable } from '../../util/UndoManager'; +import { PinDocView } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { FocusViewOptions } from '../nodes/FocusViewOptions'; -import { Docs } from '../../documents/Documents'; -import { PinDocView } from '../PinFuncs'; -import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; enum cardSortings { Time = 'time', @@ -599,22 +598,21 @@ export class CollectionCardView extends CollectionSubView() { }); focus = action((anchor: Doc, options: FocusViewOptions): Opt => { - const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); - if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return undefined; - options.didMove = true; - const target = DocCast(anchor.annotationOn) ?? anchor; - const index = docs.indexOf(target); - index !== -1 && (this._curDoc = target); + const docs = DocListCast(this.Document[this.fieldKey]); + if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) { + const foundDoc = DocCast( + anchor.config_curDoc, + docs.find(doc => doc === DocCast(anchor.annotationOn, anchor)) + ); + options.didMove = foundDoc !== this.curDoc() ? true : false; + options.didMove && (this._curDoc = foundDoc); + } return undefined; }); getAnchor = (addAsAnnotation: boolean) => { - const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document }); - anchor.config_curDoc = this.curDoc(); + const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_curDoc: this.curDoc() }); PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document); - if (addAsAnnotation) { - // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered - Doc.AddDocToList(this.dataDoc, this._props.fieldKey + '_annotations', anchor); - } + addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; // pinned / linked anchor doc includes selected rows, graph titles, and graph colors @@ -625,15 +623,7 @@ export class CollectionCardView extends CollectionSubView() { } return false; }; - addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => { - const doc = toList(docsIn).lastElement(); - const where = location.split(':')[0]; - if (where === OpenWhere.lightbox && (this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc))) { - if (doc.hidden) doc.hidden = false; - if (!location.includes(OpenWhereMod.always)) return true; - } - return this._props.addDocTab(docsIn, location); - }; + addDocTab = this.addLinkedDocTab; /** * Actually renders all the cards diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 81e921724..081e2fe5a 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -1,24 +1,23 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; +import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { returnZero } from '../../../ClientUtils'; import { Utils } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { BoolCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; +import { PinDocView } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCarousel3DView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { computedFn } from 'mobx-utils'; -import { Docs } from '../../documents/Documents'; -import { PinDocView } from '../PinFuncs'; -import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss'); @@ -94,22 +93,18 @@ export class CollectionCarousel3DView extends CollectionSubView() { .scale(1 / this.centerScale); focus = (anchor: Doc, options: FocusViewOptions): Opt => { - const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); - if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return undefined; - options.didMove = true; - const target = DocCast(anchor.annotationOn) ?? anchor; - const index = docs.indexOf(target); - index !== -1 && (this.layoutDoc._carousel_index = index); + const docs = DocListCast(this.Document[this.fieldKey]); + if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) { + const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor)); + options.didMove = newIndex !== this.layoutDoc._carousel_index; + options.didMove && (this.layoutDoc._carousel_index = newIndex); + } return undefined; }; getAnchor = (addAsAnnotation: boolean) => { - const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document }); - anchor.config_carousel_index = this.layoutDoc._carousel_index; + const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.layoutDoc._carousel_index as number }); PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document); - if (addAsAnnotation) { - // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered - Doc.AddDocToList(this.dataDoc, this._props.fieldKey + '_annotations', anchor); - } + addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; // pinned / linked anchor doc includes selected rows, graph titles, and graph colors @@ -120,16 +115,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { } return false; }; - addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => { - const doc = toList(docsIn).lastElement(); - const where = location.split(':')[0]; - if (where === OpenWhere.lightbox && (this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc))) { - if (doc.hidden) doc.hidden = false; - if (!location.includes(OpenWhereMod.always)) return true; - } - return this._props.addDocTab(docsIn, location); - }; - + addDocTab = this.addLinkedDocTab; @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); const displayDoc = (child: Doc, dxf: () => Transform) => ( diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index b043706fd..bfb58733d 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { StopEvent, returnOne, returnZero } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { BoolCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; @@ -13,7 +13,6 @@ import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; -import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; @@ -76,23 +75,19 @@ export class CollectionCarouselView extends CollectionSubView() { curDoc = () => this.carouselItems[this.carouselIndex]?.layout; focus = (anchor: Doc, options: FocusViewOptions): Opt => { - const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); - if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return undefined; - options.didMove = true; - const target = DocCast(anchor.annotationOn) ?? anchor; - const index = docs.indexOf(target); - index !== -1 && (this.layoutDoc._carousel_index = index); + const docs = DocListCast(this.Document[this.fieldKey]); + if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) { + const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor)); + options.didMove = newIndex !== this.layoutDoc._carousel_index; + options.didMove && (this.layoutDoc._carousel_index = newIndex); + } return undefined; }; getAnchor = (addAsAnnotation: boolean) => { - const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document }); - anchor.config_carousel_index = this.carouselIndex; + const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.carouselIndex }); PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document); - if (addAsAnnotation) { - // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered - Doc.AddDocToList(this.dataDoc, this._props.fieldKey + '_annotations', anchor); - } + addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; // pinned / linked anchor doc includes selected rows, graph titles, and graph colors @@ -103,15 +98,7 @@ export class CollectionCarouselView extends CollectionSubView() { } return false; }; - addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => { - const doc = toList(docsIn).lastElement(); - const where = location.split(':')[0]; - if (where === OpenWhere.lightbox && (this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc))) { - if (doc.hidden) doc.hidden = false; - if (!location.includes(OpenWhereMod.always)) return true; - } - return this._props.addDocTab(docsIn, location); - }; + addDocTab = this.addLinkedDocTab; captionStyleProvider = (doc: Doc | undefined, captionProps: Opt, property: string) => { // first look for properties on the document in the carousel, then fallback to properties on the container diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 48aac3a68..9952863d7 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; @@ -27,6 +27,7 @@ import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldViewProps } from '../nodes/FieldView'; import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FlashcardPracticeUI } from './FlashcardPracticeUI'; +import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; export interface CollectionViewProps extends React.PropsWithChildren { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) @@ -130,6 +131,17 @@ export function CollectionSubView() { @computed get childDocList() { return Cast(this.dataField, listSpec(Doc)); } + + addLinkedDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => { + const doc = toList(docsIn).lastElement(); + const where = location.split(':')[0]; + if (where === OpenWhere.lightbox && (this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc))) { + if (doc.hidden) doc.hidden = false; + if (!location.includes(OpenWhereMod.always)) return true; + } + return this._props.addDocTab(docsIn, location); + }; + collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters); collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []); // child filters apply to the descendants of the documents in this collection diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 5bfdee1f5..1c5722217 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -285,6 +285,7 @@ export class TabDocView extends ObservableReactComponent { static Activate = (tabDoc: Doc) => { const tab = Array.from(CollectionDockingView.Instance?.tabMap ?? []).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue); + if (tab.header.parent._activeContentItem === tab.contentItem) return false; tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) return tab !== undefined; }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index afd662b22..0241fc2dd 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -40,7 +40,7 @@ import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveEraserWidth, Active import { FieldViewProps } from '../../nodes/FieldView'; import { FocusViewOptions } from '../../nodes/FocusViewOptions'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { OpenWhere, OpenWhereMod } from '../../nodes/OpenWhere'; +import { OpenWhere } from '../../nodes/OpenWhere'; import { PinDocView, PinProps } from '../../PinFuncs'; import { AnnotationPalette } from '../../smartdraw/AnnotationPalette'; import { DrawingOptions, SmartDrawHandler } from '../../smartdraw/SmartDrawHandler'; @@ -1596,21 +1596,14 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout)?.includes(firstDoc)) { - if (firstDoc.hidden) firstDoc.hidden = false; - if (!location.includes(OpenWhereMod.always)) return true; - } + if (this.layoutDoc._isLightbox) { + this._lightboxDoc = docs[0]; + return true; } - break; + return this.addLinkedDocTab(docsIn, location); default: } - return this._props.addDocTab(docs, location); + return this._props.addDocTab(docsIn, location); }); getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData { const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 7448fa898..53d72e946 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -48,6 +48,7 @@ import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; import SlideEffect from './SlideEffect'; import { AnimationSettings, SpringSettings, SpringType, easeItems, effectItems, effectTimings, movementItems, presEffectDefaultTimings, springMappings, springPreviewColors } from './SpringUtils'; +import _ from 'lodash'; @observer export class PresBox extends ViewBoxBaseComponent() { @@ -708,7 +709,7 @@ export class PresBox extends ViewBoxBaseComponent() { } if ((pinDataTypes?.filters && activeItem.config_docFilters !== undefined) || (!pinDataTypes && activeItem.config_docFilters !== undefined)) { - if (bestTarget.childFilters !== activeItem.config_docFilters) { + if (!_.isEqual(Array.from(StrListCast(bestTarget.childFilters)), Array.from(StrListCast(activeItem.config_docFilters)))) { bestTarget.childFilters = ObjectField.MakeCopy(activeItem.config_docFilters as ObjectField) || new List([]); changed = true; } diff --git a/src/extensions/ExtensionsTypings.ts b/src/extensions/ExtensionsTypings.ts index d6ffd3be3..fa8851bb3 100644 --- a/src/extensions/ExtensionsTypings.ts +++ b/src/extensions/ExtensionsTypings.ts @@ -1,6 +1,14 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ interface Array { + /** + * returns the last element of the array or undefined + */ lastElement(): T; + /** + * if val is in the list, it returns its index, otherwise undefined; + * @param val + */ + getIndex(val: T): number | undefined; } interface String { diff --git a/src/extensions/Extensions_Array.ts b/src/extensions/Extensions_Array.ts index a50fb330f..d61585e28 100644 --- a/src/extensions/Extensions_Array.ts +++ b/src/extensions/Extensions_Array.ts @@ -1,14 +1,13 @@ export default class ArrayExtension { private readonly property: string; - private readonly body: (this: Array) => any; + private readonly body: (this: Array, args: unknown) => unknown; - constructor(property: string, body: (this: Array) => any) { + constructor(property: string, body: (this: Array, args: unknown) => unknown) { this.property = property; this.body = body; } assign() { - // eslint-disable-next-line no-extend-native Object.defineProperty(Array.prototype, this.property, { value: this.body, enumerable: false, @@ -28,6 +27,11 @@ const extensions = [ } return this[this.length - 1]; }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new ArrayExtension('getIndex', function (val: any) { + const index = this.indexOf(val); + return index === -1 ? undefined : index; + }), ]; function Assign() { -- cgit v1.2.3-70-g09d2 From 6d5b80adf8c0d1f93dce30401e1f90d11b838de3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 18 Oct 2024 10:00:01 -0400 Subject: fixed following anchors to collection card/carousel to set the current/selected Doc --- src/client/documents/Documents.ts | 2 +- src/client/views/PinFuncs.ts | 6 +-- src/client/views/ViewBoxInterface.ts | 2 +- .../views/collections/CollectionCardDeckView.tsx | 55 +++++++++------------- .../views/collections/CollectionCarousel3DView.tsx | 10 +--- .../views/collections/CollectionCarouselView.tsx | 10 +--- .../views/collections/CollectionTimeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 36 +++++++------- src/fields/ScriptField.ts | 6 +-- 10 files changed, 53 insertions(+), 78 deletions(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index be3b9c3bb..98e8bc932 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -373,7 +373,7 @@ export class DocumentOptions { config_panY?: NUMt = new NumInfo('panY saved as a view spec', false); config_zoom?: NUMt = new NumInfo('zoom saved as a view spec', false); config_carousel_index?: NUMt = new NumInfo('saved carousel index', false); - config_curDoc?: DOCt = new DocInfo('current doc in a collection view, e.g., cardView'); + config_card_curDoc?: DOCt = new DocInfo('current doc in a collection view, e.g., cardView'); config_viewScale?: NUMt = new NumInfo('viewScale saved as a view Spec', false); presentation_transition?: NUMt = new NumInfo('the time taken for the transition TO a document', false); presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view', false); diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts index 17964c656..bed473eec 100644 --- a/src/client/views/PinFuncs.ts +++ b/src/client/views/PinFuncs.ts @@ -16,7 +16,7 @@ export interface pinDataTypes { scrollable?: boolean; dataviz?: number[]; pannable?: boolean; - type_collection?: boolean; + collectionType?: boolean; inkable?: boolean; filters?: boolean; pivot?: boolean; @@ -65,7 +65,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { pinProps.pinData.scrollable || pinProps.pinData.temporal || pinProps.pinData.pannable || - pinProps.pinData.type_collection || + pinProps.pinData.collectionType || pinProps.pinData.clippable || pinProps.pinData.datarange || pinProps.pinData.dataview || @@ -118,7 +118,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { }) ) ); - if (pinProps.pinData.type_collection) pinDoc.config_type_collection = targetDoc._type_collection; + if (pinProps.pinData.collectionType) pinDoc.config_type_collection = targetDoc._type_collection; if (pinProps.pinData.filters) pinDoc.config_docFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField) ?? new List(); if (pinProps.pinData.pivot) pinDoc.config_pivotField = targetDoc._pivotField; if (pinProps.pinData.pannable) { diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts index f66f6062e..b7980d74e 100644 --- a/src/client/views/ViewBoxInterface.ts +++ b/src/client/views/ViewBoxInterface.ts @@ -24,7 +24,7 @@ export abstract class ViewBoxInterface

extends ObservableReactComponent void; // moves contents of collection to parent updateIcon?: (usePanelDimensions?: boolean) => Promise; // updates the icon representation of the document getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) - restoreView?: (viewSpec: Doc) => boolean; + restoreView?: (viewSpec: Doc) => boolean; // DEPRECATED: do not use, it will go away. see PresBox.restoreTargetDocView scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: FocusViewOptions) => Opt; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms) getView?: (doc: Doc, options: FocusViewOptions) => Promise>; // returns a nested DocumentView for the specified doc or undefined diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 729a1585a..beb45f326 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -51,14 +51,13 @@ export class CollectionCardView extends CollectionSubView() { private _textToDoc = new Map(); private _oldWheel: HTMLElement | null = null; private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center) - private _clickScript = () => ScriptField.MakeScript('scriptContext._curDoc=this', { scriptContext: 'any' })!; + private _setCurDocScript = () => ScriptField.MakeScript('scriptContext.layoutDoc._card_curDoc=this', { scriptContext: 'any' })!; @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap(); @observable _maxRowCount = 10; @observable _docDraggedIndex: number = -1; - @observable _curDoc: Doc | undefined = undefined; constructor(props: SubCollectionViewProps) { super(props); @@ -165,10 +164,10 @@ export class CollectionCardView extends CollectionSubView() { /** * When in quiz mode, randomly selects a document */ - quizMode = action(() => { + quizMode = () => { const randomIndex = Math.floor(Math.random() * this.childDocs.length); - this._curDoc = this.childDocs[randomIndex]; - }); + this.layoutDoc._card_curDoc = this.childDocs[randomIndex]; + }; setHoveredNodeIndex = action((index: number) => { if (!SnappingManager.IsDragging) this._hoveredNodeIndex = index; @@ -319,7 +318,7 @@ export class CollectionCardView extends CollectionSubView() { (doc: Doc) => () => this._props.isContentActive?.() === false ? false - : this._props.isDocumentActive?.() && this._curDoc === doc + : this._props.isDocumentActive?.() && this.curDoc() === doc ? true : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false @@ -347,7 +346,7 @@ export class CollectionCardView extends CollectionSubView() { scriptContext={this} focus={this.focus} onDoubleClickScript={this.onChildDoubleClick} - onClickScript={this._curDoc === doc ? undefined : this._clickScript} + onClickScript={this.curDoc() === doc ? undefined : this._setCurDocScript} dontCenter="y" // Don't center it vertically, because the grid it's in is already doing that and we don't want to do it twice. dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType} showTags={BoolCast(this.layoutDoc.showChildTags)} @@ -572,8 +571,8 @@ export class CollectionCardView extends CollectionSubView() { * @param doc doc that will be animated away from center focus */ releaseCurDoc = action(() => { - const selDoc = this._curDoc; - this._curDoc = undefined; + const selDoc = this.curDoc(); + this.layoutDoc._card_curDoc = undefined; const cardDocView = DocumentView.getDocumentView(selDoc, this.DocumentView?.()); if (cardDocView && selDoc) { DocumentView.DeselectView(cardDocView); @@ -590,7 +589,7 @@ export class CollectionCardView extends CollectionSubView() { * turns off the _dropped flag at the end of a drag/drop, or releases the focused Doc if a different Doc is clicked */ cardPointerUp = action((doc: Doc) => { - if (this._curDoc === doc || this._dropped) { + if (this.curDoc() === doc || this._dropped) { this._dropped = false; } else { this.releaseCurDoc(); // NOTE: the onClick script for the card will select the new card (ie, 'doc') @@ -601,28 +600,20 @@ export class CollectionCardView extends CollectionSubView() { const docs = DocListCast(this.Document[this.fieldKey]); if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) { const foundDoc = DocCast( - anchor.config_curDoc, + anchor.config_card_curDoc, docs.find(doc => doc === DocCast(anchor.annotationOn, anchor)) ); options.didMove = foundDoc !== this.curDoc() ? true : false; - options.didMove && (this._curDoc = foundDoc); + options.didMove && (this.layoutDoc._card_curDoc = foundDoc); } return undefined; }); getAnchor = (addAsAnnotation: boolean) => { - const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_curDoc: this.curDoc() }); - PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document); + const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_card_curDoc: this.curDoc() }); + PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; - // pinned / linked anchor doc includes selected rows, graph titles, and graph colors - restoreView = (viewData: Doc) => { - if (viewData.config_curDoc !== undefined && this.curDoc() !== viewData.config_curDoc) { - this._curDoc = DocCast(viewData.config_curDoc); - return true; - } - return false; - }; addDocTab = this.addLinkedDocTab; /** @@ -634,9 +625,9 @@ export class CollectionCardView extends CollectionSubView() { return this.sortedDocs.map((doc, index) => { const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); - const childScreenToLocal = this.childScreenToLocal(doc, index, doc === this._curDoc); + const childScreenToLocal = this.childScreenToLocal(doc, index, doc === this.curDoc()); - const translateToCenterIfActive = () => (doc === this._curDoc ? (cardsInRow / 2 - (index % this._maxRowCount)) * 100 - 50 : 0); + const translateToCenterIfActive = () => (doc === this.curDoc() ? (cardsInRow / 2 - (index % this._maxRowCount)) * 100 - 50 : 0); const aspect = NumCast(doc.height) / NumCast(doc.width, 1); const vscale = Math.max(1,Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale * this.nativeScaling) / (aspect * this.childPanelWidth()), @@ -645,15 +636,15 @@ export class CollectionCardView extends CollectionSubView() { return (

this.cardPointerUp(doc)} style={{ width: this.childPanelWidth(), height: 'max-content', - transform: `translateY(${this.adjustCardYtoFitArch(doc === this._curDoc, index)}px) + transform: `translateY(${this.adjustCardYtoFitArch(doc === this.curDoc(), index)}px) translateX(calc(${translateToCenterIfActive()}% + ${this.horizontalAdjustmentForPartialRows(index, cardsInRow)}px)) - rotate(${doc !== this._curDoc ? this.rotate(index) : 0}rad) - scale(${doc === this._curDoc ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`, + rotate(${doc !== this.curDoc()? this.rotate(index) : 0}rad) + scale(${doc === this.curDoc()? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`, }} // prettier-ignore onPointerEnter={() => this.setHoveredNodeIndex(index)} onPointerLeave={() => this.setHoveredNodeIndex(-1)}> @@ -671,10 +662,10 @@ export class CollectionCardView extends CollectionSubView() { isContentActive: emptyFunction, ScreenToLocalTransform: this.contentScreenToLocalXf, }); - answered = action(() => { - this._curDoc = this.curDoc ? this.filteredChildDocs()[(this.filteredChildDocs().findIndex(d => d === this.curDoc()) + 1) % (this.filteredChildDocs().length || 1)] : undefined; - }); - curDoc = () => this._curDoc; + answered = () => { + this.layoutDoc._card_curDoc = this.curDoc() ? this.filteredChildDocs()[(this.filteredChildDocs().findIndex(d => d === this.curDoc()) + 1) % (this.filteredChildDocs().length || 1)] : undefined; + }; + curDoc = () => DocCast(this.layoutDoc._card_curDoc); render() { const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale; diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 081e2fe5a..61a74dd7a 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -103,18 +103,10 @@ export class CollectionCarousel3DView extends CollectionSubView() { }; getAnchor = (addAsAnnotation: boolean) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.layoutDoc._carousel_index as number }); - PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document); + PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; - // pinned / linked anchor doc includes selected rows, graph titles, and graph colors - restoreView = (viewData: Doc) => { - if (viewData.config_carousel_index !== undefined && this.layoutDoc._carousel_index !== viewData.config_carousel_index) { - this.layoutDoc._carousel_index = viewData.config_carousel_index; - return true; - } - return false; - }; addDocTab = this.addLinkedDocTab; @computed get content() { const currentIndex = NumCast(this.layoutDoc._carousel_index); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index bfb58733d..f714e2a00 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -86,18 +86,10 @@ export class CollectionCarouselView extends CollectionSubView() { getAnchor = (addAsAnnotation: boolean) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.carouselIndex }); - PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document); + PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; - // pinned / linked anchor doc includes selected rows, graph titles, and graph colors - restoreView = (viewData: Doc) => { - if (viewData.config_carousel_index !== undefined && this.layoutDoc._carousel_index !== viewData.config_carousel_index) { - this.layoutDoc._carousel_index = viewData.config_carousel_index; - return true; - } - return false; - }; addDocTab = this.addLinkedDocTab; captionStyleProvider = (doc: Doc | undefined, captionProps: Opt, property: string) => { diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 5738e7ce3..d75c633ac 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -52,7 +52,7 @@ export class CollectionTimeView extends CollectionSubView() { title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as unknown as string, // title can take a functiono or a string annotationOn: this.Document, }); - PinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.Document); + PinDocView(anchor, { pinData: { collectionType: true, pivot: true, filters: true } }, this.Document); if (addAsAnnotation) { // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0241fc2dd..ff711314b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1743,7 +1743,7 @@ export class CollectionFreeFormView extends CollectionSubView() { if (typeof res === 'string') { const resObj = JSON.parse(res); console.log('Parsed GPT Result ', resObj); - // eslint-disable-next-line no-restricted-syntax for (const key in resObj) { if (resObj[key]) { console.log('typeof property', typeof resObj[key]); @@ -563,11 +562,11 @@ export class PresBox extends ViewBoxBaseComponent() { const datarange = [DocumentType.FUNCPLOT].includes(targetType); const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined; - const typeCollection = targetType === DocumentType.COL; + const collectionType = targetType === DocumentType.COL; const filters = true; const pivot = true; const dataannos = false; - return { scrollable, pannable, inkable, type_collection: typeCollection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; + return { scrollable, pannable, inkable, collectionType, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; } @action @@ -575,7 +574,6 @@ export class PresBox extends ViewBoxBaseComponent() { /* empty */ }; @action - // eslint-disable-next-line default-param-last static restoreTargetDocView(bestTargetView: Opt, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: dataTypes, targetDoc?: Doc) { const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc); if (!bestTarget) return undefined; @@ -701,7 +699,19 @@ export class PresBox extends ViewBoxBaseComponent() { changed = true; } } - if ((pinDataTypes?.type_collection && activeItem.config_type_collection !== undefined) || (!pinDataTypes && activeItem.config_type_collection !== undefined)) { + if ((pinDataTypes?.collectionType && activeItem.config_card_curDoc !== undefined) || (!pinDataTypes && activeItem.config_card_curDoc !== undefined)) { + if (bestTarget._card_curDoc !== activeItem.config_card_curDoc) { + bestTarget._card_curDoc = activeItem.config_card_curDoc; + changed = true; + } + } + if ((pinDataTypes?.collectionType && activeItem.config_carousel_index !== undefined) || (!pinDataTypes && activeItem.config_carousel_index !== undefined)) { + if (bestTarget._carousel_index !== activeItem.config_carousel_index) { + bestTarget._carousel_index = activeItem.config_carousel_index; + changed = true; + } + } + if ((pinDataTypes?.collectionType && activeItem.config_type_collection !== undefined) || (!pinDataTypes && activeItem.config_type_collection !== undefined)) { if (bestTarget._type_collection !== activeItem.config_type_collection) { bestTarget._type_collection = activeItem.config_type_collection; changed = true; @@ -1135,7 +1145,6 @@ export class PresBox extends ViewBoxBaseComponent() { return false; } } else if (doc.type !== DocumentType.PRES) { - // eslint-disable-next-line operator-assignment if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide'; doc.presentation_targetDoc = doc.createdFrom ?? doc; // dropped document will be a new embedding of an embedded document somewhere else. doc.presentation_movement = PresMovement.Zoom; @@ -1167,8 +1176,7 @@ export class PresBox extends ViewBoxBaseComponent() { const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null); if (curDoc && curDoc === this.activeItem) return ( - // eslint-disable-next-line react/no-array-index-key -
+
{index + 1}. {StrCast(curDoc.title)}) @@ -1176,15 +1184,13 @@ export class PresBox extends ViewBoxBaseComponent() { ); if (tagDoc) return ( - // eslint-disable-next-line react/no-array-index-key -
+
{index + 1}. {StrCast(curDoc.title)}
); if (curDoc) return ( - // eslint-disable-next-line react/no-array-index-key -
+
{index + 1}. {StrCast(curDoc.title)}
); @@ -1369,7 +1375,6 @@ export class PresBox extends ViewBoxBaseComponent() { const tagDoc = PresBox.targetRenderedDoc(doc); const srcContext = Cast(tagDoc.embedContainer, Doc, null); const labelCreator = (top: number, left: number, edge: number, fontSize: number) => ( - // eslint-disable-next-line react/no-array-index-key
this.selectElement(doc)}>
{index + 1}
@@ -1620,7 +1625,6 @@ export class PresBox extends ViewBoxBaseComponent() { this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true); this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true); this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true); - // eslint-disable-next-line camelcase const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem; array.forEach(curDoc => { curDoc.presentation_transition = pt; @@ -2018,7 +2022,6 @@ export class PresBox extends ViewBoxBaseComponent() {
{this.generatedAnimations.map((elem, i) => (
{ @@ -3046,7 +3049,6 @@ export class PresBox extends ViewBoxBaseComponent() {
{mode !== CollectionViewType.Invalid ? ( deserializeScript(obj as ScriptField)) export class ScriptField extends ObjectField { @serializable @@ -137,7 +138,6 @@ export class ScriptField extends ObjectField { [ToString]() { return this.script.originalScript; } - // eslint-disable-next-line default-param-last public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) { return CompileScript(script, { params: { @@ -156,13 +156,11 @@ export class ScriptField extends ObjectField { }); } - // eslint-disable-next-line default-param-last public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) { const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); return compiled.compiled ? new ScriptField(compiled) : undefined; } - // eslint-disable-next-line default-param-last public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) { const compiled = ScriptField.CompileScript(script, params, false, capturedVariables); return compiled.compiled ? new ScriptField(compiled) : undefined; @@ -186,6 +184,7 @@ export class ScriptField extends ObjectField { } @scriptingGlobal +// eslint-disable-next-line no-use-before-define @Deserializable('computed', (obj: unknown) => deserializeScript(obj as ComputedField)) export class ComputedField extends ScriptField { static undefined = '__undefined'; @@ -229,7 +228,6 @@ export class ComputedField extends ScriptField { [ToValue](doc: Doc) { return ComputedField.useComputed ? { value: this.value(doc) } : undefined; } // prettier-ignore [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore - // eslint-disable-next-line default-param-last public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) { const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables }); const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined; -- cgit v1.2.3-70-g09d2 From 050fe4fe0d9f9722a7a2a48ae3b446049f93775f Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 24 Oct 2024 11:06:27 -0400 Subject: fixed doc decorations for carousel docs. --- .../views/collections/CollectionCarousel3DView.tsx | 16 +++++++++++----- src/client/views/collections/CollectionSubView.tsx | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 61a74dd7a..c080ba27e 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -75,21 +75,27 @@ export class CollectionCarousel3DView extends CollectionSubView() { ? false : undefined ); - contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1); + contentScreenToLocalXf = () => this._props.ScreenToLocalTransform().translate(0, (-(Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) / this.nativeScaling()); childScreenLeftToLocal = () => this.contentScreenToLocalXf() - .translate(-(this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + .translate( + (-this.panelWidth() * (1 - this.sideScale)) / 2, // + (-this.panelHeight() * (1 - this.sideScale)) / 2 + ) .scale(1 / this.sideScale); childScreenRightToLocal = () => this.contentScreenToLocalXf() - .translate(-2 * this.panelWidth() - (this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + .translate( + -2 * this.panelWidth() - (this.panelWidth() * (1 - this.sideScale)) / 2, // + (-this.panelHeight() * (1 - this.sideScale)) / 2 + ) .scale(1 / this.sideScale); childCenterScreenToLocal = () => this.contentScreenToLocalXf() .translate( -this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, // Focused Doc is shifted right by 1/3 panel width then left by increased size percent of center * 1/2 * panel width / 3 - -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2 - ) // top is top margin % of panelHeight - increased size percent of center * panelHeight / 2 + ((this.centerScale - 1) * this.panelHeight()) / 2 + ) .scale(1 / this.centerScale); focus = (anchor: Doc, options: FocusViewOptions): Opt => { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 9952863d7..0c059f729 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -531,6 +531,7 @@ export function CollectionSubView() { }; protected _sideBtnWidth = 35; + protected _sideBtnMaxPanelPct = 0.15; @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined; /** * How much the content of the collection is being scaled based on its nesting and its fit-to-width settings @@ -541,7 +542,7 @@ export function CollectionSubView() { * This takes the desired screen space size and converts into collection coordinates. It then returns the smaller of the converted * size or a fraction of the collection view. */ - @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth * this.contentScaling, (this._props.fitWidth?.(this.Document) && this._props.PanelWidth() > NumCast(this.layoutDoc._width)? 1: 0.25) * NumCast(this.layoutDoc.width, 1)); } // prettier-ignore + @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth * this.contentScaling, (this._props.fitWidth?.(this.Document) && this._props.PanelWidth() > NumCast(this.layoutDoc._width)? 1: this._sideBtnMaxPanelPct) * NumCast(this.layoutDoc.width, 1)); } // prettier-ignore /** * This computes a scale factor for UI elements so that they shrink and grow as the collection does in screen space. * Note, the scale factor does not allow for elements to grow larger than their native screen space size. -- cgit v1.2.3-70-g09d2 From 6b070202843e1e2a11c09c4d29aee4ab8750f31f Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 24 Oct 2024 12:01:21 -0400 Subject: fixed pinning of card and carousel views. --- src/client/views/collections/CollectionCardDeckView.tsx | 6 +++--- src/client/views/collections/CollectionCarousel3DView.tsx | 6 +++--- src/client/views/collections/CollectionCarouselView.tsx | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index beb45f326..3a5cb5f37 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -19,7 +19,7 @@ import { dropActionType } from '../../util/DropActionTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable } from '../../util/UndoManager'; -import { PinDocView } from '../PinFuncs'; +import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; @@ -608,9 +608,9 @@ export class CollectionCardView extends CollectionSubView() { } return undefined; }); - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_card_curDoc: this.curDoc() }); - PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); + PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index c080ba27e..b7ecf9a2f 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -12,7 +12,7 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; -import { PinDocView } from '../PinFuncs'; +import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; @@ -107,9 +107,9 @@ export class CollectionCarousel3DView extends CollectionSubView() { } return undefined; }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.layoutDoc._carousel_index as number }); - PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); + PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index f714e2a00..87c6e3e5c 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -8,7 +8,7 @@ import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; -import { PinDocView } from '../PinFuncs'; +import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; @@ -84,9 +84,9 @@ export class CollectionCarouselView extends CollectionSubView() { return undefined; }; - getAnchor = (addAsAnnotation: boolean) => { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.carouselIndex }); - PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document); + PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { collectionType: true, filters: true } }, this.Document); addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered return anchor; }; -- cgit v1.2.3-70-g09d2 From 205fdacf9807be2691e13e1d9d04d6b98620391a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 15 Jan 2025 13:03:59 -0500 Subject: fixed so that users are sentt to dropbox to authorized uploads once, and then a refresh token is used afterwards. fixed up some collection views to work better with the tagsView. fixed grid view to show entire image. tweaked notetaking view to work better with tags, but it's incomplete. --- .../views/collections/CollectionCarousel3DView.tsx | 1 + .../views/collections/CollectionNoteTakingView.tsx | 11 +-- .../views/collections/CollectionStackingView.tsx | 13 ++- .../collectionGrid/CollectionGridView.tsx | 12 +-- src/client/views/smartdraw/DrawingFillHandler.tsx | 28 +++++-- src/server/ApiManagers/FireflyManager.ts | 94 ++++++++++++++++++++-- src/server/authentication/DashUserModel.ts | 5 ++ 7 files changed, 141 insertions(+), 23 deletions(-) (limited to 'src/client/views/collections/CollectionCarousel3DView.tsx') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index b7ecf9a2f..9c8ef5519 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -136,6 +136,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} + showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)} /> ); diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index ac8e37358..323a679cc 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, DivHeight, lightOrDark, returnZero, smoothScroll } from '../../../ClientUtils'; -import { Doc, Field, FieldType, Opt } from '../../../fields/Doc'; +import { Doc, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Copy, Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -48,6 +48,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { @computed get notetakingCategoryField() { return StrCast(this.dataDoc.notetaking_column, StrCast(this.layoutDoc.pivotField, 'notetaking_column')); } + toHeader = (d: Doc) => (d[this.notetakingCategoryField] instanceof List ? StrListCast(d[this.notetakingCategoryField]).join('.') : (d[this.notetakingCategoryField] ?? 'unset')); public DividerWidth = 16; @observable docsDraggedRowCol: number[] = []; @observable _scroll = 0; @@ -136,7 +137,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { const rowCol = this.docsDraggedRowCol; // this will sort the docs into the correct columns (minus the ones you're currently dragging) docs.forEach(d => { - const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`; + const sectionValue = this.toHeader(d); // look for if header exists already const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString()); if (existingHeader) { @@ -161,7 +162,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { }; @computed get allFieldValues() { - return new Set(this.childDocs.map(doc => StrCast(doc[this.notetakingCategoryField]))); + return new Set(this.childDocs.map(doc => (doc[this.notetakingCategoryField] instanceof List ? StrListCast(doc[this.notetakingCategoryField]).join('.') : StrCast(doc[this.notetakingCategoryField])))); } componentDidMount() { @@ -313,7 +314,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { // how to get the width of a document. Currently returns the width of the column (minus margins) // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...) getDocWidth = (d: Doc) => { - const heading = !d[this.notetakingCategoryField] ? 'unset' : Field.toString(d[this.notetakingCategoryField] as FieldType); + const heading = this.toHeader(d); const existingHeader = this.colHeaderData.find(sh => sh.heading === heading); const existingWidth = this.layoutDoc._notetaking_columns_autoSize ? 1 / (this.colHeaderData.length ?? 1) : existingHeader?.width ? existingHeader.width : 0; const maxWidth = existingWidth > 0 ? existingWidth * this.availableWidth : this.maxColWidth; @@ -427,7 +428,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { const colHeader = colIndex === undefined ? 'unset' : StrCast(this.colHeaderData[colIndex].heading); this.childDocs?.forEach(d => { if (d instanceof Promise) return; - const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset'; + const sectionValue = this.toHeader(d); if (sectionValue.toString() === colHeader) { docsMatchingHeader.push(d); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 1ac0b6d70..a57256424 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -146,7 +146,17 @@ export class CollectionStackingView extends CollectionSubView @@ -344,6 +354,7 @@ export class CollectionStackingView extends CollectionSubView Transform, width: () => number, height: () => number) { return ( ); } diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx index d9a01c436..bd8499806 100644 --- a/src/client/views/smartdraw/DrawingFillHandler.tsx +++ b/src/client/views/smartdraw/DrawingFillHandler.tsx @@ -10,6 +10,7 @@ import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { OpenWhere } from '../nodes/OpenWhere'; import { AspectRatioLimits, FireflyDimensionsMap, FireflyImageDimensions, FireflyStylePresets } from './FireflyConstants'; +const DashDropboxId = '2m86iveqdr9vzsa'; export class DrawingFillHandler { static drawingToImage = (drawing: Doc, strength: number, user_prompt: string) => { const docData = drawing[DocData]; @@ -33,14 +34,27 @@ export class DrawingFillHandler { const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`; return imageUrlToBase64(structureUrl) .then(gptDescribeImage) - .then(prompt => - Networking.PostToServer('/queryFireflyImageFromStructure', - { prompt: `${user_prompt || prompt}`, width: dims.width, height: dims.height, structureUrl, strength, styles }) + .then(prompt => + Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${user_prompt || prompt}`, width: dims.width, height: dims.height, structureUrl, strength, styles }) .then((info: Upload.ImageInformation) => - DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(info.accessPaths.agnostic.client, - { ai: 'firefly', title: user_prompt || prompt, ai_firefly_prompt: user_prompt || prompt, _width: 500, data_nativeWidth: info.nativeWidth, data_nativeHeight: info.nativeHeight }), OpenWhere.addRight) - ).catch(e => alert("create image failed: " + e.toString())) - ); // prettier-ignore + DocumentViewInternal.addDocTabFunc( + Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { + ai: 'firefly', + title: user_prompt || prompt, + ai_firefly_prompt: user_prompt || prompt, + _width: 500, + data_nativeWidth: info.nativeWidth, + data_nativeHeight: info.nativeHeight, + }), + OpenWhere.addRight + ) + ) + .catch(e => { + if (e.toString().includes('Dropbox') && confirm('Create image failed. Try authorizing DropBox?\r\n' + e.toString().replace(/^[^"]*/, ''))) { + window.open(`https://www.dropbox.com/oauth2/authorize?client_id=${DashDropboxId}&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox`, '_blank')?.focus(); + } + }) + ); // prettier-ignore:q } }); }; diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index a1f8fab8d..6039f5675 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -1,7 +1,9 @@ +import axios from 'axios'; import { Dropbox } from 'dropbox'; import * as fs from 'fs'; import * as multipart from 'parse-multipart-data'; import * as path from 'path'; +import { DashUserModel } from '../authentication/DashUserModel'; import { DashUploadUtils } from '../DashUploadUtils'; import { _error, _invalid, _success, Method } from '../RouteManager'; import { Directory, filesDirectory } from '../SocketData'; @@ -55,7 +57,7 @@ export default class FireflyManager extends ApiManager { ) ); - uploadImageToDropbox = (fileUrl: string, dbx = new Dropbox({ accessToken: process.env.DROPBOX_TOKEN })) => + uploadImageToDropbox = (fileUrl: string, user: DashUserModel | undefined, dbx = new Dropbox({ accessToken: user?.dropboxToken || '' })) => new Promise((res, rej) => fs.readFile(path.join(filesDirectory, `${Directory.images}/${path.basename(fileUrl)}`), undefined, (err, contents) => { if (err) { @@ -69,8 +71,32 @@ export default class FireflyManager extends ApiManager { .catch(e => res(new Error(e.toString()))); }) .catch(e => { - console.log('Dropbox error:', e); - res(new Error(e.toString())); + if (user?.dropboxRefresh) { + console.log('*********** try refresh dropbox for: ' + user.email + ' ***********'); + this.refreshDropboxToken(user).then(token => { + if (!token) { + console.log('Dropbox error: cannot refresh token'); + res(new Error(e.toString())); + } else { + const dbxNew = new Dropbox({ accessToken: user.dropboxToken || '' }); + dbxNew + .filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents }) + .then(response => { + dbxNew + .filesGetTemporaryLink({ path: response.result.path_display ?? '' }) + .then(link => res(link.result.link)) + .catch(linkErr => res(new Error(linkErr.toString()))); + }) + .catch(uploadErr => { + console.log('Dropbox error:', uploadErr); + res(new Error(uploadErr.toString())); + }); + } + }); + } else { + console.log('Dropbox error:', e); + res(new Error(e.toString())); + } }); } }) @@ -230,12 +256,35 @@ export default class FireflyManager extends ApiManager { ); return fetched; }; + + refreshDropboxToken = (user: DashUserModel) => + axios + .post( + 'https://api.dropbox.com/oauth2/token', + new URLSearchParams({ + refresh_token: user.dropboxRefresh || '', + grant_type: 'refresh_token', + client_id: process.env._CLIENT_DROPBOX_CLIENT_ID || '', + client_secret: process.env._CLIENT_DROPBOX_SECRET || '', + }).toString() + ) + .then(refresh => { + console.log('***** dropbox token refreshed for ' + user?.email + ' ******* '); + user.dropboxToken = refresh.data.access_token; + user.save(); + return user.dropboxToken; + }) + .catch(e => { + console.log(e); + return undefined; + }); + protected initialize(register: Registration): void { register({ method: Method.POST, subscription: '/queryFireflyImageFromStructure', secureHandler: async ({ req, res }) => - this.uploadImageToDropbox(req.body.structureUrl).then(uploadUrl => + this.uploadImageToDropbox(req.body.structureUrl, req.user as DashUserModel).then(uploadUrl => uploadUrl instanceof Error ? _invalid(res, uploadUrl.message) : this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploadUrl, req.body.strength, req.body.styles).then(fire => @@ -275,7 +324,7 @@ export default class FireflyManager extends ApiManager { method: Method.POST, subscription: '/expandImage', secureHandler: ({ req, res }) => - this.uploadImageToDropbox(req.body.file).then(uploadUrl => + this.uploadImageToDropbox(req.body.file, req.user as DashUserModel).then(uploadUrl => uploadUrl instanceof Error ? _invalid(res, uploadUrl.message) : this.expandImage(uploadUrl, req.body.prompt).then(text => { @@ -288,5 +337,40 @@ export default class FireflyManager extends ApiManager { }) ), }); + + // construct this url and send user to it. It will allow them to authorize their dropbox account and will send the resulting token to our endpoint /refreshDropbox + // https://www.dropbox.com/oauth2/authorize?client_id=DROPBOX_CLIENT_ID&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox + // see: https://dropbox.tech/developers/using-oauth-2-0-with-offline-access + // + register({ + method: Method.GET, + subscription: '/refreshDropbox', + secureHandler: ({ req, res }) => { + const user = req.user as DashUserModel; + console.log(`******************* dropbox authorized for ${user?.email} ******************`); + _success(res, 'dropbox authorized for ' + user?.email); + + const data = new URLSearchParams({ + code: req.query.code as string, + grant_type: 'authorization_code', + client_id: process.env._CLIENT_DROPBOX_CLIENT_ID ?? '', + client_secret: process.env._CLIENT_DROPBOX_SECRET ?? '', + redirect_uri: 'http://localhost:1050/refreshDropbox', + }); + axios + .post('https://api.dropbox.com/oauth2/token', data.toString()) + .then(response => { + console.log('***** dropbox token (and refresh) received for ' + user?.email + ' ******* '); + user.dropboxToken = response.data.access_token; + user.dropboxRefresh = response.data.refresh_token; + user.save(); + + setTimeout(() => this.refreshDropboxToken(user), response.data.expires_in - 600); + }) + .catch(e => { + console.log(e); + }); + }, + }); } } diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts index bfa6d7bdb..debeef60c 100644 --- a/src/server/authentication/DashUserModel.ts +++ b/src/server/authentication/DashUserModel.ts @@ -9,6 +9,9 @@ export type DashUserModel = mongoose.Document & { passwordResetToken?: string; passwordResetExpires?: Date; + dropboxRefresh?: string; + dropboxToken?: string; + userDocumentId: string; sharingDocumentId: string; linkDatabaseId: string; @@ -37,6 +40,8 @@ const userSchema = new mongoose.Schema( passwordResetToken: String, passwordResetExpires: Date, + dropboxRefresh: String, + dropboxToken: String, userDocumentId: String, // id that identifies a document which hosts all of a user's account data sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users linkDatabaseId: String, -- cgit v1.2.3-70-g09d2