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/CollectionCardDeckView.tsx | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'src/client/views/collections/CollectionCardDeckView.tsx') diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index b86dad9d7..60305244c 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -9,7 +9,7 @@ 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 } from '../../../fields/Types'; +import { BoolCast, DateCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { URLField } from '../../../fields/URLField'; import { gptImageLabel } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -25,6 +25,9 @@ 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', @@ -604,6 +607,33 @@ export class CollectionCardView extends CollectionSubView() { index !== -1 && (this._curDoc = target); return undefined; }); + getAnchor = (addAsAnnotation: boolean) => { + const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document }); + anchor.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); + } + 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 = (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); + }; /** * Actually renders all the cards -- 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/CollectionCardDeckView.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/CollectionCardDeckView.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 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/CollectionCardDeckView.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 63f9f9573dab9745d2f570e1917b325a474fdd93 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 28 Oct 2024 20:07:51 -0400 Subject: reworked LabelBox's to be fully editable. gor rid of singleLine for label boxes. made a text_fitBox field to render text with a labelBox. fixed styleprovider in freeformview clusters to be untracked() to avoid invalidations. added a default text color to Settings panel. fixed clicking text buttons to not lose focus in labelBox. --- package-lock.json | 8 +- package.json | 2 +- src/client/documents/DocUtils.ts | 5 +- src/client/documents/Documents.ts | 14 +- src/client/util/CurrentUserUtils.ts | 9 +- src/client/util/SettingsManager.tsx | 24 +- src/client/views/MarqueeAnnotator.tsx | 3 +- src/client/views/PinFuncs.ts | 4 +- src/client/views/SidebarAnnos.tsx | 4 +- src/client/views/StyleProvider.tsx | 4 +- src/client/views/StyleProviderQuiz.tsx | 2 +- .../views/collections/CollectionCardDeckView.tsx | 1 - .../CollectionFreeFormClusters.ts | 6 +- .../collections/collectionSchema/SchemaRowBox.tsx | 2 +- src/client/views/global/globalScripts.ts | 24 +- .../views/nodes/DataVizBox/DocCreatorMenu.tsx | 35 +-- src/client/views/nodes/DocumentView.tsx | 12 +- src/client/views/nodes/EquationBox.tsx | 2 +- .../views/nodes/FontIconBox/FontIconBox.scss | 4 + src/client/views/nodes/FontIconBox/FontIconBox.tsx | 4 +- src/client/views/nodes/LabelBox.scss | 1 - src/client/views/nodes/LabelBox.tsx | 168 +++++++++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 259 +++++++++++---------- .../formattedText/ProsemirrorExampleTransfer.ts | 4 +- .../views/nodes/formattedText/RichTextMenu.tsx | 71 +++--- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/nodes/trails/PresBox.tsx | 14 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 23 +- src/fields/documentSchemas.ts | 7 +- 29 files changed, 426 insertions(+), 292 deletions(-) (limited to 'src/client/views/collections/CollectionCardDeckView.tsx') diff --git a/package-lock.json b/package-lock.json index 4e95fcee0..43dcb1c3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,7 +72,7 @@ "body-parser": "^1.20.2", "bootstrap": "^5.3.2", "brotli": "^1.3.3", - "browndash-components": "^0.1.50", + "browndash-components": "0.1.53", "browser-assert": "^1.2.1", "bson": "^6.2.0", "canvas": "^2.11.2", @@ -13575,9 +13575,9 @@ } }, "node_modules/browndash-components": { - "version": "0.1.50", - "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.50.tgz", - "integrity": "sha512-8EgU82os8/Tg3gayh+nYXCQZ8P7GGQWnibz0U2RM0sXTbc3BWTSYiC1Sj1VpL7HPkZ1KMGHzWynCo9QKIOWZBg==", + "version": "0.1.53", + "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.53.tgz", + "integrity": "sha512-lBZlXe2u/6d9NlrR+fH+9ncCFGRUxGqJRejpfhbhVmQ4Q4uvP9LlmTuaVQElqys5wGUyK0SuqrLYPGhJthdQLQ==", "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", diff --git a/package.json b/package.json index 06179fd7d..63d5e387d 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "body-parser": "^1.20.2", "bootstrap": "^5.3.2", "brotli": "^1.3.3", - "browndash-components": "^0.1.50", + "browndash-components": "0.1.53", "browser-assert": "^1.2.1", "bson": "^6.2.0", "canvas": "^2.11.2", diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 19f3c89ef..e3cb5e72c 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -679,10 +679,13 @@ export namespace DocUtils { ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance : { _width: width || 200, - _height: 35, + _height: BoolCast(Doc.UserDoc().fitBox) ? 70 : 35, _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), _layout_fitWidth: true, _layout_autoHeight: true, + text_fitBox: BoolCast(Doc.UserDoc().fitBox), + text_align: StrCast(Doc.UserDoc().textAlign), + text_fontColor: StrCast(Doc.UserDoc().fontColor), }), }); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 98e8bc932..b0e1a7545 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -294,7 +294,6 @@ export class DocumentOptions { _yMargin?: NUMt = new NumInfo('gap between top edge of dcoument and start of masonry/stacking layouts', false); _xPadding?: NUMt = new NumInfo('x padding', false); _yPadding?: NUMt = new NumInfo('y padding', false); - _singleLine?: boolean; // whether label box is restricted to one line of text _createDocOnCR?: boolean; // whether carriage returns and tabs create new text documents _columnWidth?: NUMt = new NumInfo('width of table column', false); _columnsHideIfEmpty?: BOOLt = new BoolInfo('whether stacking view column headings should be hidden'); @@ -302,10 +301,14 @@ export class DocumentOptions { _caption_yMargin?: NUMt = new NumInfo('y margin of caption inside of a carousel collection', false, true); icon_nativeWidth?: NUMt = new NumInfo('native width of icon view', false, true); icon_nativeHeight?: NUMt = new NumInfo('native height of icon view', false, true); - _text_fontSize?: string; - _text_fontFamily?: string; - _text_fontWeight?: string; - text_align?: STRt = new StrInfo('horizontal text alignment default'); + text_fontSize?: string; + text_fontFamily?: string; + text_fontWeight?: string; + text_fitBox?: BOOLt = new BoolInfo("whether text box should be scaled to fit it's containing render box"); + text_align?: STRt = new StrInfo('horizontal text alignment default', undefined, undefined, ['left', 'center', 'right']); + title_align?: STRt = new StrInfo('horizontal title alignment in label box', undefined, undefined, ['left', 'center', 'right']); + title_transform?: STRt = new StrInfo('transformation to apply to title in label box (eg., uppercase)', undefined, undefined, ['uppercase', 'lowercase', 'capitalize']); + text_transform?: STRt = new StrInfo('transformation to apply to text in text box (eg., uppercase)', undefined, undefined, ['uppercase', 'lowercase', 'capitalize']); text_placeholder?: BOOLt = new BoolInfo('makes the text act like a placeholder and automatically select when the text box is selected'); fontSize?: string; _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views @@ -496,7 +499,6 @@ export class DocumentOptions { sidebar_type_collection?: string; // collection type of text sidebar data_dashboards?: List; // list of dashboards used in shareddocs; - textTransform?: string; letterSpacing?: string; iconTemplate?: string; // name of icon template style icon_fieldKey?: string; // specifies the icon template to use (e.g., icon_fieldKey='george', then the icon template's name is icon_george; otherwise, the template's name would be icon_ where type is the Doc's type(pdf,rich text, etc)) diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 30c75c659..b3aa5e145 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -186,7 +186,7 @@ export class CurrentUserUtils { const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({ - layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts + layout: LabelBox.LayoutString(fieldKey), letterSpacing: "unset", _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts }); const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts }); const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); @@ -268,7 +268,7 @@ export class CurrentUserUtils { MakeTemplate(Docs.Create.MultirowDocument( [ Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200, isSystem: true }), - Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) }) + Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, text_fontFamily: StrCast(Doc.UserDoc().fontFamily), text_fontSize: StrCast(Doc.UserDoc().fontSize) }) ], {...opts, title: "Slide View Template"})); const plotlyApi = () => { let plotly = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@plotly"); @@ -392,7 +392,7 @@ pie title Minerals in my tap water {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, _layout_dontCenter:'xy', dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }}, {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree, - treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, + treeView_HasOverlay: true, text_fontSize: "20px", _layout_autoHeight: true, dropAction:dropActionType.move, treeView_Type: TreeViewType.outline, backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true }, funcs: {title: 'this.text?.Text'}}, @@ -731,7 +731,7 @@ pie title Minerals in my tap water return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, - { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 }, + { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 9 }, { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'} }, { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'} }, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, @@ -746,6 +746,7 @@ pie title Minerals in my tap water { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, ]}, + { title: "Fit Box", toolTip: "Fit text to box", btnType: ButtonType.ToggleButton, icon: "object-group",toolType:"fitBox", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Elide", toolTip: "Elide selection", btnType: ButtonType.ToggleButton, icon: "eye", toolType:"elide", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 9200d68db..33d7d90a8 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { Button, ColorPicker, Colors, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -45,7 +45,6 @@ export class SettingsManager extends React.Component { public closeMgr = action(() => { this._isOpen = false; }); - // eslint-disable-next-line react/no-unused-class-component-methods public openMgr = action(() => { this._isOpen = true; }); @@ -354,6 +353,27 @@ export class SettingsManager extends React.Component { Doc.UserDoc().fontSize = val + 'px'; }} /> + } + tooltip="default text background color" + label="background" + setSelectedColor={value => { + Doc.UserDoc().textBackgroundColor = value; + // if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`); + // this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false }); + }} + setFinalColor={value => { + Doc.UserDoc().textBackgroundColor = value; + // this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false }); + // this.colorBatch?.end(); + // this.colorBatch = undefined; + }} + /> ({ text: val, diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 7266875c5..fa1123a2d 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -34,7 +34,7 @@ export interface MarqueeAnnotatorProps { getPageFromScroll?: (top: number) => number; finishMarquee: (x?: number, y?: number) => void; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); - anchorMenuFlashcard?: () => Promise; + anchorMenuFlashcard?: () => Promise; anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined; highlightDragSrcColor?: string; } @@ -220,7 +220,6 @@ export class MarqueeAnnotator extends ObservableReactComponent (cropRegion = this.highlight('', true, undefined, true)); // hyperlink color const targetCreator = (/* annotationOn: Doc | undefined */) => this.props.anchorMenuCrop!(cropRegion, false)!; DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts index bed473eec..ab02c2d07 100644 --- a/src/client/views/PinFuncs.ts +++ b/src/client/views/PinFuncs.ts @@ -1,4 +1,4 @@ -import { Doc, DocListCast } from '../../fields/Doc'; +import { Doc, DocListCast, Field } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { Copy, Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; @@ -74,7 +74,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { const fkey = Doc.LayoutFieldKey(targetDoc); if (pinProps.pinData.dataview) { pinDoc.config_usePath = targetDoc[fkey + '_usePath']; - pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; + pinDoc.config_data = Field.Copy(targetDoc[fkey]); } if (pinProps.pinData.dataannos) { const fieldKey = Doc.LayoutFieldKey(targetDoc); diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 1f3ad8444..87076bf65 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -78,8 +78,8 @@ export class SidebarAnnos extends ObservableReactComponent, props: Opt, props: Opt, props: Opt, property: string) => { - if (doc && this.childDocs?.includes(doc)) + // without untracked, every inquired style property for any Doc will be invalidated if a change is made to the collection's childDocs. + // this prevents that by assuming that a Doc is generally always (or never) a member of childDocs - if it's removed or added, then all of its properties get updated anyway. + if (doc && untracked(() => this.childDocs)?.includes(doc)) switch (property.split(':')[0]) { case StyleProp.BackgroundColor: { diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 6ffb0865a..7f519b065 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -188,7 +188,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { selectedCells={this.selectedCells} selectedCol={this.selectedCol} setColumnValues={this.setColumnValues} - oneLine={BoolCast(this.schemaDoc?._singleLine)} + oneLine={BoolCast(this.schemaDoc?._schema_singleLine)} menuTarget={this.schemaView.MenuTarget} transform={() => { const ind = index === this.schemaView.columnKeys.length - 1 ? this.schemaView.columnKeys.length - 3 : index; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 954c79f7d..013b18a97 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -295,10 +295,10 @@ ScriptingGlobals.add(function setTagFilter(tag: string, added: boolean, checkRes }, ''); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: string | number, checkResult?: boolean) { +ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize', value: string | number, checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; // prettier-ignore - const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => string | undefined; setDoc: () => void;}> = new Map([ + const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => string | undefined; setDoc: () => void;}> = new Map([ ['font', { checkResult: () => RichTextMenu.Instance?.fontFamily, setDoc: () => value && RichTextMenu.Instance?.setFontField(value.toString(), 'fontFamily'), @@ -311,10 +311,6 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh checkResult: () => RichTextMenu.Instance?.fontColor, setDoc: () => value && RichTextMenu.Instance?.setFontField(value.toString(), 'fontColor'), }], - ['alignment', { - checkResult: () => RichTextMenu.Instance?.textAlign, - setDoc: () => { value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value.toString() as "center"|"left"|"right"):(Doc.UserDoc().textAlign = value); }, - }], ['fontSize', { checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), setDoc: () => { @@ -334,7 +330,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh return undefined; }); -type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; +type attrname = 'noAutoLink' | 'dictation' | 'fitBox' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => unknown }]; // eslint-disable-next-line prefer-arrow-callback @@ -343,14 +339,10 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: const editorView = textView?.EditorView; // prettier-ignore const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) => - [ where, { checkResult: () => editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false: - (RichTextMenu.Instance?.textAlign === where)): - where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered): - (Doc.UserDoc().textAlign === where), - toggle: () => { editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(): - RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)): - where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered: - (Doc.UserDoc().textAlign = where); } + [ where, { checkResult: () => (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false: + (RichTextMenu.Instance?.textAlign === where)), + toggle: () => { (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(): + RichTextMenu.Instance?.align(editorView, editorView?.dispatch, where)); } }]); // prettier-ignore // prettier-ignore const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => @@ -360,6 +352,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: const attrs:attrfuncs[] = [ ['dictation', { checkResult: () => !!textView?._recordingDictation, toggle: () => textView && runInAction(() => { textView._recordingDictation = !textView._recordingDictation;} ) }], + ['fitBox', { checkResult: () => RichTextMenu.Instance?.fitBox ?? false, + toggle: () => RichTextMenu.Instance?.toggleFitBox()}], ['elide', { checkResult: () => false, toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}], ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false), diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx index 16c016d6c..7c601185e 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx @@ -245,7 +245,7 @@ export class DocCreatorMenu extends ObservableReactComponent { } updateIcons = (docs: Doc[]) => { - console.log('called') + console.log('called'); docs.map(this.getIcon); }; @@ -416,7 +416,7 @@ export class DocCreatorMenu extends ObservableReactComponent { const height = bottom - top; const width = right - left; const doc = !field.title.includes('$$') - ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height / 2}` }) + ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, text_fontSize: `${height / 2}` }) : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/\$\$/g, ''), x: left, y: top }); return doc; }); @@ -919,17 +919,17 @@ export class DocCreatorMenu extends ObservableReactComponent { @action setExpandedView = (info: { icon: ImageField; doc: Doc } | undefined) => { if (info) { const doc = info.doc; - const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); - const newInfo = {icon: new ImageField(''), doc: wrapper} + const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: '' }); + const newInfo = { icon: new ImageField(''), doc: wrapper }; this._expandedPreview = newInfo; } else { this._expandedPreview = info; } }; - get editingWindow(){ + get editingWindow() { const doc = this._expandedPreview?.doc ?? new Doc(); - const rendered = + const rendered = (
{ removeDocument={returnFalse} PanelWidth={() => this._menuDimensions.width - 10} PanelHeight={() => this._menuDimensions.height - 60} - ScreenToLocalTransform={() => new Transform(-this._pageX,-this._pageY, 1)} + ScreenToLocalTransform={() => new Transform(-this._pageX, -this._pageY, 1)} renderDepth={5} whenChildContentsActiveChanged={emptyFunction} focus={emptyFunction} @@ -961,14 +961,21 @@ export class DocCreatorMenu extends ObservableReactComponent { yPadding={0} />
- + ); return (
-
+
{rendered}
-
- ); } @@ -2260,11 +2266,11 @@ export class FieldUtils { title: title, x: coord.x, y: coord.y, - _text_fontSize: `${FieldUtils.calculateFontSize(width, height, content, true)}`, + text_fontSize: `${FieldUtils.calculateFontSize(width, height, content, true)}`, backgroundColor: opts.backgroundColor ?? '', text_fontColor: opts.color, contentBold: opts.fontBold, - textTransform: opts.fontTransform, + text_transform: opts.fontTransform, color: opts.color, _layout_borderRounding: `${opts.cornerRounding ?? 0}px`, borderColor: opts.borderColor, @@ -2306,7 +2312,7 @@ export class FieldUtils { public static CarouselField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, fields: Doc[]) => { const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); - const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, _text_fontSize: `${height / 2}` }); + const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, text_fontSize: `${height / 2}` }); return doc; }; @@ -2359,4 +2365,3 @@ export class FieldUtils { // }] // }; // } - diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a343b9a39..eb7f333b9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -880,7 +880,7 @@ export class DocumentViewInternal extends DocComponent{renderDoc} - case PresEffect.Flip: return {renderDoc} - case PresEffect.Rotate: return {renderDoc} - case PresEffect.Bounce: return {renderDoc} - case PresEffect.Roll: return {renderDoc} + const presEffect = StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect)); + switch (presEffect) { + case PresEffect.Expand: case PresEffect.Flip: case PresEffect.Rotate: case PresEffect.Bounce: + case PresEffect.Roll: return {renderDoc} // case PresEffect.Fade: return {renderDoc} case PresEffect.Fade: return {renderDoc} // keep as preset, doesn't really make sense with spring config diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index fefe25764..290c90d6e 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -122,7 +122,7 @@ export class EquationBox extends ViewBoxBaseComponent() { width: 'fit-content', // `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: !this._props.isSelected() ? 'none' : undefined, - fontSize: StrCast(this.layoutDoc._text_fontSize), + fontSize: StrCast(this.Document._text_fontSize), }} onKeyDown={e => e.stopPropagation()}> diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss index 2db285910..ab03a2318 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss @@ -10,6 +10,10 @@ height: 3px !important; } } +.fonticonbox { + margin: auto; + width: 100%; +} .menuButton { height: 100%; display: flex; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index d4898eb3c..b45774a75 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -262,11 +262,13 @@ export class FontIconBox extends ViewBoxBaseComponent() { const script = ScriptCast(this.Document.onClick)?.script; const toggleStatus = script?.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean; + // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string; const background = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string; const items = DocListCast(this.dataDoc.data); const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType)); + return ( () { render() { return ( -
+
{this.renderButton()}
); diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss index ca4b3d467..889cdc0ca 100644 --- a/src/client/views/nodes/LabelBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -13,7 +13,6 @@ height: 100%; border-radius: inherit; //letter-spacing: 2px; // bcz: doesn't work with LabelBigText - text-transform: uppercase; overflow: hidden; display: inline-block; margin: auto; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 94a9541f2..dcf9e1fed 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,19 +1,22 @@ import { Property } from 'csstype'; -import { action, computed, makeObservable } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as textfit from 'textfit'; -import { Field, FieldType } from '../../../fields/Doc'; -import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; +import { Doc, Field } from '../../../fields/Doc'; +import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; +import { undoable } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { FieldView, FieldViewProps } from './FieldView'; import './LabelBox.scss'; +import { FormattedTextBox } from './formattedText/FormattedTextBox'; +import { RichTextMenu } from './formattedText/RichTextMenu'; @observer export class LabelBox extends ViewBoxBaseComponent() { @@ -22,7 +25,8 @@ export class LabelBox extends ViewBoxBaseComponent() { } private dropDisposer?: DragManager.DragDropDisposer; private _timeout: NodeJS.Timeout | undefined; - _divRef: HTMLDivElement | null = null; + private _divRef: HTMLDivElement | null = null; + private _reaction: IReactionDisposer | undefined; constructor(props: FieldViewProps) { super(props); @@ -36,21 +40,29 @@ export class LabelBox extends ViewBoxBaseComponent() { } }; - @computed get Title() { - return Field.toString(this.dataDoc[this.fieldKey] as FieldType) || StrCast(this.Document.title); - } - - @computed get backgroundColor() { - return this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string; - } - componentDidMount() { this._props.setContentViewBox?.(this); + this._reaction = reaction( + () => this.Title, + () => document.activeElement !== this._divRef && this._forceRerender++ + ); } componentWillUnMount() { this._timeout && clearTimeout(this._timeout); + this.setText(this._divRef?.innerText ?? ''); + this._reaction?.(); } + @observable _forceRerender = 0; + + @computed get Title() { return Field.toString(this.dataDoc[this.fieldKey]); } // prettier-ignore + @computed get backgroundColor() { return this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string; } // prettier-ignore + @computed get boxShadow() { return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BoxShadow) as string; } // prettier-ignore + + setText = undoable((text: string) => { + this.dataDoc[this.fieldKey] = text; + }, 'set label text'); + drop = (/* e: Event, de: DragManager.DropEvent */) => { return false; }; @@ -82,10 +94,11 @@ export class LabelBox extends ViewBoxBaseComponent() { const textfitParams = { minFontSize: NumCast(this.layoutDoc._label_minFontSize, 1), maxFontSize: NumCast(this.layoutDoc._label_maxFontSize, 100), - multiLine: BoolCast(this.layoutDoc._singleLine, true) ? false : true, - alignHoriz: true, + multiLine: r?.textContent?.includes('\n') ? true : false, + // hack because tetFit doesn't support align 'right', but we need mobx to invalidate, so treat null as false and set to right inline + alignHoriz: StrCast(this.layoutDoc[this.fieldKey + '_align']) === 'center' ? true : StrCast(this.layoutDoc[this.fieldKey + '_align']) === 'right' ? (null as unknown as boolean) : false, alignVert: true, - detectMultiLine: true, + detectMultiLine: false, }; if (r) { if (!r.offsetHeight || !r.offsetWidth) { @@ -94,65 +107,140 @@ export class LabelBox extends ViewBoxBaseComponent() { this._timeout = setTimeout(() => this.fitTextToBox(r)); return textfitParams; } + r.style.whiteSpace = ''; // textfit sets to nowrap if not multiline, but doesn't reeset if it becomes multiline + r.style.textAlign = StrCast(this.layoutDoc[this.fieldKey + '_align']); // textfit doesn't reset textAlign if it has been set to center, so we just set it to what we want + r.firstChild instanceof HTMLElement && (r.firstChild.style.textAlign = StrCast(this.layoutDoc[this.fieldKey + '_align'])); textfit(r, textfitParams); } return textfitParams; }; + resetCursor = (cranchor?: number) => { + if (this._divRef && (cranchor || this._divRef === document.activeElement)) { + const range = document.createRange(); + const anchor = cranchor ?? this._divRef.childNodes.length; + const container = cranchor === undefined ? this._divRef : (this._divRef.firstChild?.firstChild ?? this._divRef); + range.setStart(container, anchor); + range.setEnd(container, anchor); + const sel = window.getSelection(); + sel?.removeAllRanges(); + sel?.addRange(range); + } + }; + + beforeInput = action((event: InputEvent) => { + const spanChild = this._divRef?.firstChild?.firstChild; + if (spanChild?.nodeName === '#text' && ['insertLineBreak', 'insertParagraph'].includes(event.inputType)) { + event.preventDefault(); + event.stopPropagation(); + + const selection = document.getSelection(); + if (selection && document.activeElement === event.target) { + const text = spanChild.textContent ?? ''; + const cranchor = selection.anchorNode === this._divRef ? (selection.anchorOffset ? text.length : 0) : selection.anchorOffset; + const addReturnHack = text.length <= cranchor && text[text.length - 1] !== '\n' ? '\n\n' : '\n'; // not sure why, but need to add a second carriage return if typing enter at the end of the text + const splitText = text.substring(0, cranchor) + addReturnHack + text.substring(cranchor); + spanChild.textContent = splitText; + this.resetCursor(cranchor + addReturnHack.length); + } + // const span = document.createElement('span'); + // span.innerHTML = '​'; + // this._divRef!.append(span); + } + }); + // .labelBox-mainButton > div > span:nth-child(2) { + + /** + * When an IconButton is clicked, it will receive focus. However, we don't want that since we want or need that since we really want + * to maintain focus in the label's editing div (and cursor position). so this relies on IconButton's having a tabindex set to -1 so that + * we can march up the tree from the 'relatedTarget' to determine if the loss of focus was caused by a fonticonbox. If it is, we then + * restore focus + * @param e focusout event on the editing div + */ + keepFocus = (e: FocusEvent) => { + if (e.relatedTarget instanceof HTMLElement && e.relatedTarget.tabIndex === -1) { + for (let ele: HTMLElement | null = e.relatedTarget; ele; ele = (ele as HTMLElement)?.parentElement) { + if ((ele as HTMLElement)?.className === 'fonticonbox') { + setTimeout(() => this._divRef?.focus()); + break; + } + } + } + }; + render() { TraceMobx(); const boxParams = this.fitTextToBox(undefined); // this causes mobx to trigger re-render when data changes - const label = this.Title.startsWith('#') ? null : this.Title; return ( -
+
{ + onKeyDown={e => { e.stopPropagation(); - })} + }} onKeyUp={action(e => { e.stopPropagation(); - this.dataDoc[this.fieldKey] = this._divRef?.innerText ?? ''; - setTimeout(() => this._props.select(false)); + const text = this._divRef?.firstChild; + if (text && (text as HTMLElement)?.nodeType === 3) { + this._divRef?.removeChild(text); + this._divRef?.firstChild?.appendChild(text); + this.resetCursor(); + } + this.fitTextToBox(this._divRef); })} + onFocus={() => { + RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, this.dataDoc); + this._divRef?.removeEventListener('focusout', this.keepFocus); + this._divRef?.addEventListener('focusout', this.keepFocus); + }} onBlur={() => { - this.dataDoc[this.fieldKey] = this._divRef?.innerText ?? ''; + this._divRef?.removeEventListener('focusout', this.keepFocus); + this.setText(this._divRef?.innerText ?? ''); + RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined); + FormattedTextBox.LiveTextUndo?.end(); + FormattedTextBox.LiveTextUndo = undefined; + }} + dangerouslySetInnerHTML={{ + __html: `${this.Title.startsWith('#') ? null : (this.Title ?? '')}`, }} contentEditable={this._props.onClickScript?.() ? undefined : true} ref={r => { + this._divRef?.removeEventListener('beforeinput', this.beforeInput); this._divRef = r; - this.fitTextToBox(r); - if (this._props.isSelected() && this._divRef) { - const range = document.createRange(); - range.setStart(this._divRef, this._divRef.childNodes.length); - range.setEnd(this._divRef, this._divRef.childNodes.length); - const sel = window.getSelection(); - sel?.removeAllRanges(); - sel?.addRange(range); + if (this._divRef) { + this._divRef.addEventListener('beforeinput', this.beforeInput); + + if (Doc.SelectOnLoad === this.Document) { + Doc.SelectOnLoad = undefined; + this._divRef.focus(); + } + this.fitTextToBox(this._divRef); + if (this.Title) { + this.resetCursor(); + } } - }}> - {label} -
+ }} + />
); @@ -161,9 +249,9 @@ export class LabelBox extends ViewBoxBaseComponent() { Docs.Prototypes.TemplateMap.set(DocumentType.LABEL, { layout: { view: LabelBox, dataField: 'title' }, - options: { acl: '', _singleLine: true, _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true }, + options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, title_align: 'center', title_transform: 'uppercase' }, }); Docs.Prototypes.TemplateMap.set(DocumentType.BUTTON, { layout: { view: LabelBox, dataField: 'title' }, - options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true }, + options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true, title_align: 'center', title_transform: 'uppercase' }, }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2aee7b6b3..659cda9dd 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -65,6 +65,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { Property } from 'csstype'; +import { LabelBox } from '../LabelBox'; // import * as applyDevTools from 'prosemirror-dev-tools'; export interface FormattedTextBoxProps extends FieldViewProps { @@ -158,6 +159,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent void = unimplementedFunction; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; @@ -175,10 +177,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const rootDoc: Doc = Doc.isTemplateDoc(this._props.docViewPath().lastElement()?.Document) ? this.Document : DocCast(this.Document.rootDocument, this.Document); - if (!pinProps && this._editorView?.state.selection.empty) return rootDoc; + if (!pinProps && this.EditorView?.state.selection.empty) return rootDoc; const anchor = Docs.Create.ConfigDocument({ title: StrCast(rootDoc.title), annotationOn: rootDoc }); this.addDocument(anchor); this._finishingLink = true; @@ -263,7 +265,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight'), 'highlght text'); + AnchorMenu.Instance.Highlight = undoable((color: string) => this.EditorView?.state && RichTextMenu.Instance?.setFontField(color, 'fontHighlight'), 'highlght text'); AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true); AnchorMenu.Instance.StartCropDrag = unimplementedFunction; /** @@ -292,7 +294,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent; try { @@ -309,7 +311,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (node.type === this._editorView?.state.schema.nodes.dashField) { + if (node.type === this.EditorView?.state.schema.nodes.dashField) { const refDoc = !node.attrs.docId ? DocCast(this.Document.rootDocument, this.Document) : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); const fieldKey = StrCast(node.attrs.fieldKey); return ( @@ -320,16 +322,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && !this._editorView.isDestroyed) { - const state = this._editorView.state.apply(tx); - this._editorView.updateState(state); + if (this.EditorView && !this.EditorView.isDestroyed) { + const state = this.EditorView.state.apply(tx); + this.EditorView.updateState(state); this.tryUpdateDoc(false); } }; tryUpdateDoc = (force: boolean) => { - if (this._editorView) { - const { state } = this._editorView; + if (this.EditorView) { + const { state } = this.EditorView; const { dataDoc } = this; const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText); const newJson = JSON.stringify(state.toJSON()); @@ -372,7 +374,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent term.title).forEach(term => { tr = this.hyperlinkTerm(tr, term, newAutoLinks); }); tr = tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to))); - this._editorView?.dispatch(tr); + this.EditorView?.dispatch(tr); } oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc)); }; @@ -450,11 +452,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent) => { let tr = trIn; - const editorView = this._editorView; + const editorView = this.EditorView; if (editorView && !Doc.AreProtosEqual(target, this.Document)) { const autoLinkTerm = Field.toString(target.title as FieldType).replace(/^@/, ''); let alink: Doc | undefined; @@ -553,18 +555,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView) { - const { state } = this._editorView; + if (this.EditorView) { + const { state } = this.EditorView; if (state) { const mark = state.schema.mark(state.schema.marks.search_highlight); const activeMark = state.schema.mark(state.schema.marks.search_highlight, { selected: true }); const end = state.doc.nodeSize - 2; - this._editorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + this.EditorView.dispatch(state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); } } }; adoptAnnotation = (start: number, end: number, mark: Mark) => { - const view = this._editorView!; + const view = this.EditorView!; const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: ClientUtils.CurrentUserEmail() }); view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); }; @@ -616,7 +618,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent node that wraps the hyerlink while (target && (!(target instanceof HTMLElement) || !target.dataset?.targethrefs)) target = target.parentElement; - const editor = this._editorView; + const editor = this.EditorView; if (editor && target && !(e.nativeEvent instanceof simMouseEvent ? e.nativeEvent.dash : false)) { const hrefs = (target.dataset?.targethrefs as string) ?.trim() @@ -996,8 +998,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (resIndex < newText.length) { - const marks = this._editorView?.state.storedMarks ?? []; - this._editorView?.dispatch(this._editorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks)); + const marks = this.EditorView?.state.storedMarks ?? []; + this.EditorView?.dispatch(this.EditorView?.state.tr.insertText(newText[resIndex]).setStoredMarks(marks)); setTimeout(() => this.animateRes(resIndex + 1, newText), 20); } }; @@ -1035,8 +1035,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && this._recordingDictation) { + if (this.EditorView && this._recordingDictation) { this.stopDictation(/* true */); this._break = true; - const { state } = this._editorView; + const { state } = this.EditorView; const { to } = state.selection; const updated = TextSelection.create(state.doc, to, to); - this._editorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({}))); + this.EditorView.dispatch(state.tr.setSelection(updated).insert(to, state.schema.nodes.paragraph.create({}))); if (this._recordingDictation) { this.recordDictation(); } @@ -1082,7 +1082,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent DictationManager.Controls.stop(/* !abort */); setDictationContent = (value: string) => { - if (this._editorView && this._recordingStart) { + if (this.EditorView && this._recordingStart) { if (this._break) { const textanchorFunc = () => { const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' }); @@ -1095,22 +1095,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const examinedNode = findAnchorNode(node, editor); - if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this._editorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this._editorView?.state.schema.nodes.audiotag)) { + if (examinedNode?.node && (examinedNode.node.textContent || examinedNode.node.type === this.EditorView?.state.schema.nodes.dashDoc || examinedNode.node.type === this.EditorView?.state.schema.nodes.audiotag)) { nodes.push(examinedNode.node); !hadStart && (start = index + examinedNode.start); hadStart = true; @@ -1186,13 +1186,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (node.type === this._editorView?.state.schema.nodes.audiotag) { + if (node.type === this.EditorView?.state.schema.nodes.audiotag) { if (node.attrs.textId === textAnchorId) { return { node, start: 0 }; } return undefined; } - if (node.type === this._editorView?.state.schema.nodes.dashDoc) { + if (node.type === this.EditorView?.state.schema.nodes.dashDoc) { if (node.attrs.docId === textAnchorId) { return { node, start: 0 }; } @@ -1208,9 +1208,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent= 0 && (ret.frag.size || (firstChild && [state.schema.nodes.dashDoc, state.schema.nodes.audioTag].includes(firstChild.type)))) { @@ -1219,7 +1219,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId; addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' }); setTimeout(() => { @@ -1307,15 +1307,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && this._applyingChange !== this.fieldKey) { + if (this.EditorView && this._applyingChange !== this.fieldKey) { if (incomingValue?.data) { const updatedState = JSON.parse(incomingValue.data.Data); - if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { - this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); + if (JSON.stringify(this.EditorView.state.toJSON()) !== JSON.stringify(updatedState)) { + this.EditorView.updateState(EditorState.fromJSON(this.config, updatedState)); this.tryUpdateScrollHeight(); } - } else if (this._editorView.state.doc.textContent !== incomingValue?.str) { - selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); + } else if (this.EditorView.state.doc.textContent !== (incomingValue?.str ?? '')) { + selectAll(this.EditorView.state, tx => this.EditorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); } } }, @@ -1333,9 +1333,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (selected && this.dataDoc[this.fieldKey + '_placeholder']) { setTimeout(() => { - selectAll(this._editorView!.state, (tx: Transaction) => { - this._editorView?.dispatch(tx); - this._editorView!.focus(); + selectAll(this.EditorView!.state, (tx: Transaction) => { + this.EditorView?.dispatch(tx); + this.EditorView!.focus(); }); }); } @@ -1343,12 +1343,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const view = this._editorView!; + const view = this.EditorView!; if (pdfAnchorId) { DocServer.GetRefField(pdfAnchorId).then(pdfAnchor => { if (pdfAnchor instanceof Doc) { @@ -1487,7 +1487,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { @@ -1519,14 +1519,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - this._editorView?.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign }))); + this.EditorView?.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign }))); }); } if (startupText) { - this._editorView?.dispatch(this._editorView.state.tr.insertText(startupText)); + this.EditorView?.dispatch(this.EditorView.state.tr.insertText(startupText)); } this.tryUpdateDoc(true); } @@ -1539,56 +1539,57 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type !== mark.type), mark]; - const tr1 = this._editorView.state.tr.setStoredMarks(storedMarks); - const tr2 = selLoadChar === 'Enter' ? tr1.insert(this._editorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this._editorView.state.doc.content.size - 1); + const tr1 = this.EditorView.state.tr.setStoredMarks(storedMarks); + const tr2 = selLoadChar === 'Enter' ? tr1.insert(this.EditorView.state.doc.content.size - 1, schema.nodes.paragraph.create()) : tr1.insertText(selLoadChar, this.EditorView.state.doc.content.size - 1); const tr = tr2.setStoredMarks(storedMarks); - this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); + this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data } else if (!FormattedTextBox.DontSelectInitialText) { const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); - selectAll(this._editorView.state, (tx: Transaction) => { - this._editorView?.dispatch(tx.addStoredMark(mark)); + selectAll(this.EditorView.state, (tx: Transaction) => { + this.EditorView?.dispatch(tx.addStoredMark(mark)); }); + this.EditorView?.dispatch(this.EditorView.state.tr.setSelection(new TextSelection(this.EditorView.state.doc.resolve(1)))); this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data } else { - const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; + const $from = this.EditorView.state.selection.anchor ? this.EditorView.state.doc.resolve(this.EditorView.state.selection.anchor - 1) : undefined; const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); - const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; + const curMarks = this.EditorView.state.storedMarks ?? $from?.marksAcross(this.EditorView.state.selection.$head) ?? []; const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; - const { tr } = this._editorView.state; - this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks)); + const { tr } = this.EditorView.state; + this.EditorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))).setStoredMarks(storedMarks)); this.tryUpdateDoc(true); // calling select() above will make isContentActive() true only after a render .. which means the selectAll() above won't write to the Document and the incomingValue will overwrite the selection with the non-updated data } } if (selectOnLoad) { FormattedTextBox.DontSelectInitialText = false; - this._editorView!.focus(); + this.EditorView!.focus(); } if (this._props.isContentActive()) this.prepareForTyping(); - if (this._editorView && FormattedTextBox.PasteOnLoad) { + if (this.EditorView && FormattedTextBox.PasteOnLoad) { const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor'); FormattedTextBox.PasteOnLoad = undefined; pdfAnchorId && this.addPdfReference(pdfAnchorId); } - if (this._props.autoFocus) setTimeout(() => this._editorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it. + if (this._props.autoFocus) setTimeout(() => this.EditorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it. } // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. prepareForTyping = () => { - if (this._editorView) { + if (this.EditorView) { const { text, paragraph } = schema.nodes; - const selNode = this._editorView.state.selection.$anchor.node(); - if (this._editorView.state.selection.from === 1 && this._editorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { + const selNode = this.EditorView.state.selection.$anchor.node(); + if (this.EditorView.state.selection.from === 1 && this.EditorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })]; - this._editorView.state.selection.empty && this._editorView.state.selection.from === 1 && this._editorView?.dispatch(this._editorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor)); + this.EditorView.state.selection.empty && this.EditorView.state.selection.from === 1 && this.EditorView?.dispatch(this.EditorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor)); } } }; @@ -1602,7 +1603,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const pos = ipos ?? (this._editorView?.state.selection.$from.pos || 1); - setTimeout(() => this._editorView?.dispatch(this._editorView.state.tr.setSelection(TextSelection.near(this._editorView.state.doc.resolve(pos)))), 100); + const pos = ipos ?? (this.EditorView?.state.selection.$from.pos || 1); + setTimeout(() => this.EditorView?.dispatch(this.EditorView.state.tr.setSelection(TextSelection.near(this.EditorView.state.doc.resolve(pos)))), 100); setTimeout(() => (this.ProseRef?.children?.[0] as HTMLElement).focus(), 200); }; @action onFocused = (e: React.FocusEvent): void => { - // applyDevTools.applyDevTools(this._editorView); + // applyDevTools.applyDevTools(this.EditorView); e.stopPropagation(); }; onClick = (e: React.MouseEvent): void => { if (!this._props.isContentActive()) return; - const editorView = this._editorView; + const editorView = this.EditorView; const editorRoot = editorView?.root instanceof Document ? editorView.root : undefined; if (editorView && (!this._forceUncollapse || editorRoot?.getSelection()?.isCollapsed)) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. const pcords = editorView.posAtCoords({ left: e.clientX, top: e.clientY }); const node = pcords && editorView.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) if (pcords && node?.type === editorView.state.schema.nodes.dashComment) { - this._editorView!.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, pcords.pos + 2))); + this.EditorView!.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, pcords.pos + 2))); e.preventDefault(); } if (!node && this.ProseRef) { @@ -1726,33 +1727,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this.ProseRef?.children[0] !== e.nativeEvent.target) return; if (!(this.EditorView?.state.selection instanceof NodeSelection) || this.EditorView.state.selection.node.type !== this.EditorView.state.schema.nodes.footnote) { - const stordMarks = this._editorView?.state.storedMarks?.slice(); + const stordMarks = this.EditorView?.state.storedMarks?.slice(); if (!(this.EditorView?.state.selection instanceof NodeSelection)) { this.autoLink(); - if (this._editorView?.state.tr) { + if (this.EditorView?.state.tr) { const tr = stordMarks?.reduce((tr2, m) => { tr2.addStoredMark(m); return tr2; - }, this._editorView.state.tr); - tr && this._editorView.dispatch(tr); + }, this.EditorView.state.tr); + tr && this.EditorView.dispatch(tr); } } } - if (RichTextMenu.Instance?.view === this._editorView && !(this._props.isContentActive() || this._props.rootSelected?.())) { + if (RichTextMenu.Instance?.view === this.EditorView && !(this._props.isContentActive() || this._props.rootSelected?.())) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined); } @@ -1831,7 +1832,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema)); + this.EditorView?.dispatch(updateBullets(this.EditorView.state.tr, this.EditorView.state.schema)); e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. }; onScroll = (e: React.UIEvent) => { @@ -1872,7 +1873,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; - if (children && !SnappingManager.IsDragging) { + if (this.EditorView && children && !SnappingManager.IsDragging) { const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0; const toNum = (val: string) => Number(val.replace('px', '')); const toHgt = (node: Element): number => { @@ -2093,7 +2094,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent - return styleFromLayout?.height === '0px' ? null : ( + return this.isLabel ? ( + + ) : styleFromLayout?.height === '0px' ? null : (
{ @@ -2156,8 +2159,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 7a8b72be0..3c84e5a10 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -349,7 +349,9 @@ export function buildKeymap>(schema: S, props: any): KeyMa dispatch(tx4); } - if (view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) { + if (view.state.selection.$anchor.depth > 0 && + view.state.selection.$anchor.node(view.state.selection.$anchor.depth-1).type === schema.nodes.list_item && + view.state.selection.$anchor.nodeAfter?.type === schema.nodes.text && once) { // if text is selected across list items, then we need to forcibly insert a new line since the splitBlock code joins the two list items. enter(view.state, dispatch, view, false); } diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 55e6a3a5b..65a415a23 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx'; +import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { lift, toggleMark, wrapIn } from 'prosemirror-commands'; import { Mark, MarkType } from 'prosemirror-model'; @@ -32,7 +32,7 @@ export class RichTextMenu extends AntimodeMenu { public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable private _linkToRef = React.createRef(); - layoutDoc: Doc | undefined; + dataDoc: Doc | undefined; @observable public view?: EditorView & { TextView?: FormattedTextBox } = undefined; public editorProps: FieldViewProps | AntimodeMenuProps | undefined; @@ -49,6 +49,7 @@ export class RichTextMenu extends AntimodeMenu { @observable private _activeFontSize: string = '13px'; @observable private _activeFontFamily: string = ''; + @observable private _activeFitBox: boolean = false; @observable private _activeListType: string = ''; @observable private _activeAlignment: string = 'left'; @@ -64,13 +65,12 @@ export class RichTextMenu extends AntimodeMenu { @observable private currentLink: string | undefined = ''; @observable private showLinkDropdown: boolean = false; - _reaction: IReactionDisposer | undefined; constructor(props: AntimodeMenuProps) { super(props); makeObservable(this); runInAction(() => { RichTextMenu._instance.menu = this; - this.updateMenu(undefined, undefined, props, this.layoutDoc); + this.updateMenu(undefined, undefined, props, this.dataDoc); this._canFade = false; this.Pinned = true; }); @@ -101,6 +101,9 @@ export class RichTextMenu extends AntimodeMenu { @computed get fontHighlight() { return this._activeHighlightColor; } + @computed get fitBox() { + return this._activeFitBox; + } @computed get fontFamily() { return this._activeFontFamily; } @@ -114,26 +117,16 @@ export class RichTextMenu extends AntimodeMenu { return this._activeAlignment; } @computed get textVcenter() { - return BoolCast(this.layoutDoc?._layout_centered); - } - _disposer: IReactionDisposer | undefined; - componentDidMount() { - // this._disposer = reaction( - // () => DocumentView.Selected().slice(), - // () => this.updateMenu(undefined, undefined, undefined, undefined) - // ); - } - componentWillUnmount() { - this._disposer?.(); + return BoolCast(this.dataDoc?._layout_centered, BoolCast(Doc.UserDoc().layout_centered)); } @action - public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: FormattedTextBoxProps | AntimodeMenuProps | undefined, layoutDoc: Doc | undefined) { + public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: FormattedTextBoxProps | AntimodeMenuProps | undefined, dataDoc: Doc | undefined) { if (this._linkToRef.current?.getBoundingClientRect().width) { return; } this.view = view; - this.layoutDoc = layoutDoc; + this.dataDoc = dataDoc; props && (this.editorProps = props); // Don't do anything if the document/selection didn't change @@ -147,12 +140,13 @@ export class RichTextMenu extends AntimodeMenu { const { activeSizes } = active; const { activeColors } = active; const { activeHighlights } = active; - const refDoc = DocumentView.Selected().lastElement()?.layoutDoc ?? Doc.UserDoc(); + const refDoc = DocumentView.Selected().lastElement()?.dataDoc ?? Doc.UserDoc(); const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey); const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt)); this._activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); + this._activeFitBox = BoolCast(refDoc[refField + 'fitBox'], BoolCast(Doc.UserDoc().fitBox)); this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, refVal('fontFamily', 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, refVal('fontSize', '10px')) : activeSizes[0]; this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, refVal('fontColor', 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; @@ -181,7 +175,6 @@ export class RichTextMenu extends AntimodeMenu { toggleMark(mark.type, mark.attrs)(state, dispatch); } } - // this.updateMenu(this.view, undefined, undefined, this.layoutDoc); } }; @@ -195,8 +188,10 @@ export class RichTextMenu extends AntimodeMenu { return node.attrs.align || 'left'; } } + } else if (this.dataDoc) { + return StrCast(this.dataDoc.text_align) || 'left'; } - return 'left'; + return StrCast(Doc.UserDoc().textAlign) || 'left'; }; // finds font sizes and families in selection @@ -330,6 +325,17 @@ export class RichTextMenu extends AntimodeMenu { this.view.focus(); } }; + toggleFitBox = () => { + if (this.dataDoc) { + const doc = this.dataDoc; + (document.activeElement as HTMLElement)?.blur(); + doc.text_fitBox = !doc.text_fitBox; + } else { + Doc.UserDoc().fitBox = !Doc.UserDoc().fitBox; + Doc.UserDoc().textAlign = Doc.UserDoc().fitBox ? 'center' : undefined; + } + this.updateMenu(undefined, undefined, undefined, this.dataDoc); + }; toggleBold = () => { if (this.view) { const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); @@ -354,11 +360,14 @@ export class RichTextMenu extends AntimodeMenu { } }; - setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { - if (this.TextView && this.view) { + setFontField = (value: string, fontField: 'fitBox' | 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { + if (this.dataDoc) { + this.dataDoc[`text_${fontField}`] = value; + this.updateMenu(undefined, undefined, undefined, this.dataDoc); + } else if (this.TextView && this.view) { const { text, paragraph } = this.view.state.schema.nodes; const selNode = this.view.state.selection.$anchor.node(); - if (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { + if ((fontField === 'fontSize' && value === '0px') || (this.view.state.selection.from === 1 && this.view.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type))) { this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value; this.view.focus(); } @@ -369,7 +378,6 @@ export class RichTextMenu extends AntimodeMenu { this.view.focus(); } else { Doc.UserDoc()[fontField] = value; - // this.updateMenu(this.view, undefined, this.props, this.layoutDoc); } }; @@ -395,7 +403,6 @@ export class RichTextMenu extends AntimodeMenu { this.view!.dispatch(tx3); }); this.view.focus(); - // this.updateMenu(this.view, undefined, this.props, this.layoutDoc); }; insertSummarizer(state: EditorState, dispatch: (tr: Transaction) => void) { @@ -410,10 +417,11 @@ export class RichTextMenu extends AntimodeMenu { } vcenterToggle = () => { - this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered); + if (this.dataDoc) this.dataDoc._layout_centered = !this.dataDoc._layout_centered; + else Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered; }; - align = (view: EditorView, dispatch: (tr: Transaction) => void, alignment: 'left' | 'right' | 'center') => { - if (this.RootSelected) { + align = (view: EditorView | undefined, dispatch: undefined | ((tr: Transaction) => void), alignment: 'left' | 'right' | 'center') => { + if (view && dispatch && this.RootSelected) { let { tr } = view.state; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => { if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) { @@ -425,6 +433,11 @@ export class RichTextMenu extends AntimodeMenu { }); view.focus(); dispatch?.(tr); + } else { + if (this.dataDoc) { + this.dataDoc.text_align = alignment; + } else Doc.UserDoc().textAlign = alignment; + this.updateMenu(undefined, undefined, undefined, this.dataDoc); } }; @@ -702,7 +715,7 @@ interface RichTextMenuPluginProps { } export class RichTextMenuPlugin extends React.Component { update(view: EditorView & { TextView?: FormattedTextBox }, lastState: EditorState | undefined) { - RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.layoutDoc); + RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, view.TextView?.dataDoc); } render() { return null; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index f58434906..3d14ce4c0 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -121,7 +121,7 @@ export class RichTextRules { annotationOn: textDoc, _layout_fitWidth: true, _layout_autoHeight: true, - _text_fontSize: '9px', + text_fontSize: '9px', title: 'inline comment', }); textDocInline.title = inlineFieldKey; // give the annotation its own title diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 5d3672aee..06869717a 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -2616,13 +2616,13 @@ export class PresBox extends ViewBoxBaseComponent() { createTemplate = (layout: string, input?: string) => { const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0; const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0; - const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _text_fontSize: '24pt' }); - const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _text_fontSize: '16pt' }); - const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _text_fontSize: '20pt' }); - const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _text_fontSize: '24pt' }); - const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _text_fontSize: '14pt' }); - const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _text_fontSize: '14pt' }); - const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' }); + const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, text_fontSize: '24pt' }); + const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, text_fontSize: '16pt' }); + const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, text_fontSize: '20pt' }); + const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, text_fontSize: '24pt' }); + const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, text_fontSize: '14pt' }); + const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, text_fontSize: '14pt' }); + const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, text_fontSize: '14pt' }); // prettier-ignore switch (layout) { case 'blank': return Docs.Create.FreeformDocument([], { title: input || 'Blank slide', _width: 400, _height: 225, x, y }); diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index d5f5f620c..a7e78bcea 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -572,18 +572,19 @@ export class GPTPopup extends ObservableReactComponent {
{this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')} <> - {!this.cardsDoneLoading ? ( -
-
- - {this.loading ? Loading... : Reading Cards...} + { + !this.cardsDoneLoading ? ( +
+
+ + {this.loading ? Loading... : Reading Cards...} +
-
- ) : this.mode === GPTPopupMode.CARD ? ( - this.cardMenu() - ) : ( - this.cardActual(this.mode) - ) // Call the functions to render JSX + ) : this.mode === GPTPopupMode.CARD ? ( + this.cardMenu() + ) : ( + this.cardActual(this.mode) + ) // Call the functions to render JSX }
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 335683270..b27816f55 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -48,8 +48,8 @@ export const documentSchema = createSchema({ _columnsHideIfEmpty: 'boolean', // whether empty stacking view column headings should be hidden // _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry // _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views - _text_fontSize: 'string', - _text_fontFamily: 'string', + text_fontSize: 'string', + text_fontFamily: 'string', _layout_sidebarWidthPercent: 'string', // percent of text window width taken up by sidebar // appearance properties on the data document @@ -70,7 +70,7 @@ export const documentSchema = createSchema({ stroke_startMarker: 'string', stroke_endMarker: 'string', stroke_dash: 'string', - textTransform: 'string', + text_transform: 'string', treeView_Open: 'boolean', // flag denoting whether the documents sub-tree (contents) is visible or hidden treeView_ExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree treeView_ExpandedViewLock: 'boolean', // whether the expanded view can be changed @@ -112,5 +112,4 @@ export const collectionSchema = createSchema({ }); export type Document = makeInterface<[typeof documentSchema]>; -// eslint-disable-next-line no-redeclare export const Document = makeInterface(documentSchema); -- cgit v1.2.3-70-g09d2 From b6411c0c053db2b4495d0c545c491d9f18238e21 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 1 Nov 2024 10:53:37 -0400 Subject: fixed resize problem where NaN is generated by scaling very narrow in a card view. --- src/ClientUtils.ts | 2 +- src/client/documents/Documents.ts | 2 ++ src/client/views/DocumentDecorations.tsx | 4 ++-- src/client/views/collections/CollectionCardDeckView.tsx | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections/CollectionCardDeckView.tsx') diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index baad9a06c..8f62b9060 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -165,7 +165,7 @@ export namespace ClientUtils { return { scale: 0, translateX: 1, translateY: 1 }; } const rect = ele.getBoundingClientRect(); - const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth; + const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / (ele.offsetWidth || 1); const translateX = rect.left; const translateY = rect.top; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index efd6ce3c9..d898fe0c5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -926,6 +926,8 @@ export namespace Docs { I.stroke_isInkMask = isInkMask; I.text_align = 'center'; I.rotation = 0; + I.width_min = 1; + I.height_min = 1; I.defaultDoubleClick = 'ignore'; I.author_date = new DateField(); I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 5a48b6c62..66043c033 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -559,8 +559,8 @@ export class DocumentDecorations extends ObservableReactComponent Date: Fri, 1 Nov 2024 12:04:28 -0400 Subject: fixed inifnite loop in card view caused by bug in formattedText of adding tagsView height to doc height. made selected docs also be focused docs in cardview. fixed tags text color to be set to black --- src/client/views/TagsView.scss | 1 + src/client/views/collections/CollectionCardDeckView.tsx | 12 ++++++++++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) (limited to 'src/client/views/collections/CollectionCardDeckView.tsx') diff --git a/src/client/views/TagsView.scss b/src/client/views/TagsView.scss index 24f9e86bc..b741e7bfd 100644 --- a/src/client/views/TagsView.scss +++ b/src/client/views/TagsView.scss @@ -59,6 +59,7 @@ margin: auto; align-self: center; width: 90%; + color: black; } .tagsView-buttons { diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 39d9fe69d..836a5a2c3 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,4 +1,4 @@ -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; @@ -113,6 +113,12 @@ export class CollectionCardView extends CollectionSubView() { } } ); + this._disposers.select = reaction( + () => this.childDocs.find(d => this._docRefs.get(d)?.IsSelected), + selected => { + selected && (this.layoutDoc._card_curDoc = selected); + } + ); } componentWillUnmount() { @@ -349,7 +355,7 @@ export class CollectionCardView extends CollectionSubView() { 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)} + showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} dontHideOnDrag /> @@ -620,6 +626,7 @@ export class CollectionCardView extends CollectionSubView() { * Actually renders all the cards */ @computed get renderCards() { + trace(); // Map sorted documents to their rendered components return this.sortedDocs.map((doc, index) => { const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); @@ -667,6 +674,7 @@ export class CollectionCardView extends CollectionSubView() { curDoc = () => DocCast(this.layoutDoc._card_curDoc); render() { + trace(); const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale; return (
({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins, tagsHeight: this.tagsHeight }), - ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight, tagsHeight }) => { - const newHeight = this.contentScaling * (tagsHeight + marginsHeight + Math.max(sidebarHeight, textHeight)); + () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }), + ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => { + const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); if ( (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && // layoutAutoHeight && -- cgit v1.2.3-70-g09d2 From e7b0b92adf3404bc24e1b9d5d6a857a0ad6e6418 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Jan 2025 10:53:58 -0500 Subject: cleaning up card deck view. --- src/client/apis/gpt/GPT.ts | 26 ++++++++-- src/client/views/TagsView.tsx | 2 +- .../views/collections/CollectionCardDeckView.scss | 5 +- .../views/collections/CollectionCardDeckView.tsx | 60 +++++++++++++++++++--- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 49 +++++++++--------- 5 files changed, 101 insertions(+), 41 deletions(-) (limited to 'src/client/views/collections/CollectionCardDeckView.tsx') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index cf3a28a8e..d96972784 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -16,10 +16,10 @@ enum GPTCallType { PRONUNCIATION = 'pronunciation', DRAW = 'draw', COLOR = 'color', - RUBRIC = 'rubric', - TYPE = 'type', - SUBSET = 'subset', - INFO = 'info', + RUBRIC = 'rubric', // needs to be filled in below + TYPE = 'type', // needs to be filled in below + SUBSET = 'subset', // needs to be filled in below + INFO = 'info', // needs to be filled in below TEMPLATE = 'template', VIZSUM = 'vizsum', VIZSUM2 = 'vizsum2', @@ -84,6 +84,18 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { temp: 0.1, //0.3 prompt: "BRIEFLY (<50 words) describe any differences between the rubric and the user's answer answer in second person. If there are no differences, say correct", }, + type: { + model: 'gpt-4-turbo', + maxTokens: 512, + temp: 0.5, + prompt: '', + }, + info: { + model: 'gpt-4-turbo', + maxTokens: 512, + temp: 0.5, + prompt: '', + }, template: { model: 'gpt-4-turbo', maxTokens: 512, @@ -131,8 +143,12 @@ let lastResp = ''; * @returns AI Output */ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: string, dontCache?: boolean) => { - const inputText = [GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ, GPTCallType.STACK].includes(callType) ? inputTextIn + '.' : inputTextIn; + const inputText = inputTextIn + ([GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ, GPTCallType.STACK].includes(callType) ? '.' : ''); const opts = callTypeMap[callType]; + if (!opts) { + console.log('The query type:' + callType + ' requires a configuration.'); + return 'Error connecting with API.'; + } if (lastCall === inputText && dontCache !== true) return lastResp; try { lastCall = inputText; diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 73f404596..9e936ea54 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -9,7 +9,7 @@ import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; -import { DocCast, NumCast, StrCast } from '../../fields/Types'; +import { DocCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; import { SnappingManager } from '../util/SnappingManager'; diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 5283601bf..79c53db08 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -28,11 +28,14 @@ .collectionCardView-cardwrapper { display: grid; - grid-template-columns: repeat(10, 1fr); transform-origin: left 50%; align-items: center; z-index: 0; // so that setting z-index of active card doesn't make it land on top of things outside of the card-wrapper } +.collectionCardView-cardSizeDragger { + position: absolute; + top: 0; +} .no-card-span { position: relative; diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 836a5a2c3..800eb4914 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,8 +1,9 @@ -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, trace } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; -import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero } from '../../../ClientUtils'; +import * as CSS from 'csstype'; +import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Animation, DocData } from '../../../fields/DocSymbols'; @@ -18,7 +19,7 @@ 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 { undoable, UndoManager } from '../../util/UndoManager'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; @@ -27,6 +28,8 @@ import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { SettingsManager } from '../../util/SettingsManager'; enum cardSortings { Time = 'time', @@ -52,12 +55,13 @@ export class CollectionCardView extends CollectionSubView() { private _oldWheel: HTMLElement | null = null; private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center) private _setCurDocScript = () => ScriptField.MakeScript('scriptContext.layoutDoc._card_curDoc=this', { scriptContext: 'any' })!; + private _draggerRef = React.createRef(); @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap(); - @observable _maxRowCount = 10; @observable _docDraggedIndex: number = -1; + @observable _cursor: CSS.Property.Cursor = 'ew-resize'; constructor(props: SubCollectionViewProps) { super(props); @@ -74,6 +78,12 @@ export class CollectionCardView extends CollectionSubView() { // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); }; + @computed get cardWidth() { + return NumCast(this.layoutDoc._cardWidth, 50); + } + @computed get _maxRowCount() { + return Math.ceil(this.cardDeckWidth / this.cardWidth); + } /** * Callback to ensure gpt's text versions of the child docs are updated */ @@ -167,6 +177,14 @@ export class CollectionCardView extends CollectionSubView() { return this._props.NativeDimScaling?.() || 1; } + @computed get xMargin() { + return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); + } + + @computed get cardDeckWidth() { + return this._props.PanelWidth() - 2 * this.xMargin; + } + /** * When in quiz mode, randomly selects a document */ @@ -505,7 +523,6 @@ export class CollectionCardView extends CollectionSubView() { const normalizedItem = item.trim(); // find the corresponding Doc in the textToDoc map const doc = this._textToDoc.get(normalizedItem); - if (doc) { switch (questionType) { case '6': @@ -513,9 +530,7 @@ export class CollectionCardView extends CollectionSubView() { break; case '1': { const allHotKeys = Doc.MyFilterHotKeys; - let myTag = ''; - if (tag) { for (let i = 0; i < allHotKeys.length; i++) { // bcz: CHECK THIS CODE OUT -- SOMETHING CHANGED @@ -526,7 +541,7 @@ export class CollectionCardView extends CollectionSubView() { } } - if (myTag != '') { + if (myTag) { doc[myTag] = true; } } @@ -591,6 +606,26 @@ export class CollectionCardView extends CollectionSubView() { } }); + cardSizerDown = (e: React.PointerEvent) => { + runInAction(() => { + this._cursor = 'grabbing'; + }); + const batch = UndoManager.StartBatch('card view size'); + setupMoveUpEvents( + this, + e, + (e: PointerEvent, down: number[], delta: number[]) => { + this.layoutDoc._cardWidth = Math.max(10, delta[0] < 0 ? Math.floor(this.cardWidth + delta[0]) : Math.ceil(this.cardWidth + delta[0])); + return false; + }, + action(() => { + this._cursor = 'ew-resize'; + batch.end(); + }), + emptyFunction + ); + }; + /** * turns off the _dropped flag at the end of a drag/drop, or releases the focused Doc if a different Doc is clicked */ @@ -701,6 +736,7 @@ export class CollectionCardView extends CollectionSubView() {
@@ -717,6 +753,14 @@ export class CollectionCardView extends CollectionSubView() { {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)}
+ +
+ +
); } diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 22ca9dcd7..4028ef479 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, IconButton, Type } from '@dash/components'; -import { action, makeObservable, observable } from 'mobx'; +import { action, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { CgClose, CgCornerUpLeft } from 'react-icons/cg'; @@ -37,10 +37,8 @@ export enum GPTQuizType { MULTIPLE = 2, } -interface GPTPopupProps {} - @observer -export class GPTPopup extends ObservableReactComponent { +export class GPTPopup extends ObservableReactComponent { // eslint-disable-next-line no-use-before-define static Instance: GPTPopup; private messagesEndRef: React.RefObject; @@ -188,16 +186,9 @@ export class GPTPopup extends ObservableReactComponent { this.setLoading(true); this.setSortDone(false); - const quizType = this.quizMode; - const selected = DocumentView.SelectedDocs().lastElement(); const questionText = 'Question: ' + StrCast(selected['gptInputText']); - - if (StrCast(selected['gptRubric']) === '') { - const rubricText = 'Rubric: ' + (await this.generateRubric(StrCast(selected['gptInputText']), selected)); - } - const rubricText = 'Rubric: ' + StrCast(selected['gptRubric']); const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; @@ -214,7 +205,7 @@ export class GPTPopup extends ObservableReactComponent { this.setLoading(false); this.setSortDone(true); } catch (err) { - console.error('GPT call failed'); + console.error('GPT call failed', err); } if (this.onQuizRandom) { @@ -234,7 +225,7 @@ export class GPTPopup extends ObservableReactComponent { doc['gptRubric'] = res; return res; } catch (err) { - console.error('GPT call failed'); + console.error('GPT call failed', err); } }; @@ -262,7 +253,6 @@ export class GPTPopup extends ObservableReactComponent { * Generates a response to the user's question depending on the type of their question */ generateCard = async () => { - console.log(this.chatSortPrompt + 'USER PROMPT'); this.setLoading(true); this.setSortDone(false); @@ -271,10 +261,8 @@ export class GPTPopup extends ObservableReactComponent { } try { - // const res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); const questionType = await gptAPICall(this.chatSortPrompt, GPTCallType.TYPE); const questionNumber = questionType.split(' ')[0]; - console.log(questionType); let res = ''; switch (questionNumber) { @@ -287,10 +275,12 @@ export class GPTPopup extends ObservableReactComponent { res = await gptAPICall(this.sortDesc, GPTCallType.SORT, this.chatSortPrompt); break; default: - const selected = DocumentView.SelectedDocs().lastElement(); - const questionText = StrCast(selected!['gptInputText']); + { + const selected = DocumentView.SelectedDocs().lastElement(); + const questionText = StrCast(selected?.gptInputText); - res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt); + res = await gptAPICall(questionText, GPTCallType.INFO, this.chatSortPrompt); + } break; } @@ -308,7 +298,7 @@ export class GPTPopup extends ObservableReactComponent { // Set the extracted explanation to sortRespText this.setSortRespText(explanation); - this.conversationArray.push(this.sortRespText); + runInAction(() => this.conversationArray.push(this.sortRespText)); this.scrollToBottom(); console.log(res); @@ -448,7 +438,7 @@ export class GPTPopup extends ObservableReactComponent { private getPreviewUrl = (source: string) => source.split('.').join('_m.'); - constructor(props: GPTPopupProps) { + constructor(props: object) { super(props); makeObservable(this); GPTPopup.Instance = this; @@ -515,18 +505,25 @@ export class GPTPopup extends ObservableReactComponent { ); - handleKeyPress = async (e: React.KeyboardEvent, isSort: boolean) => { + @action + handleKeyPress = (e: React.KeyboardEvent, isSort: boolean) => { if (e.key === 'Enter') { e.stopPropagation(); if (isSort) { this.conversationArray.push(this.chatSortPrompt); - await this.generateCard(); - this.chatSortPrompt = ''; + this.generateCard().then( + action(() => { + this.chatSortPrompt = ''; + }) + ); } else { this.conversationArray.push(this.quizAnswer); - await this.generateQuiz(); - this.quizAnswer = ''; + this.generateQuiz().then( + action(() => { + this.quizAnswer = ''; + }) + ); } this.scrollToBottom(); -- cgit v1.2.3-70-g09d2 From 519a28c4ef5b5a70d29377c9baed50b455459ebd Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Jan 2025 12:02:37 -0500 Subject: card view cleanup. moved 'pile' into shiftclick on Perspective dropdown. fixed tags sorting. --- .../src/components/Dropdown/Dropdown.tsx | 371 ++++++++++----------- .../components/src/components/ListBox/ListBox.tsx | 90 ++--- .../src/components/ListItem/ListItem.tsx | 197 +++++------ src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/animationtimeline/Timeline.tsx | 14 +- .../views/collections/CollectionCardDeckView.tsx | 3 +- src/client/views/global/globalScripts.ts | 36 +- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 9 +- 8 files changed, 313 insertions(+), 411 deletions(-) (limited to 'src/client/views/collections/CollectionCardDeckView.tsx') diff --git a/packages/components/src/components/Dropdown/Dropdown.tsx b/packages/components/src/components/Dropdown/Dropdown.tsx index d9fec5e9d..0953f412c 100644 --- a/packages/components/src/components/Dropdown/Dropdown.tsx +++ b/packages/components/src/components/Dropdown/Dropdown.tsx @@ -1,31 +1,32 @@ -import React, { useEffect, useState } from 'react' -import { FaCaretDown, FaCaretLeft, FaCaretRight, FaCaretUp } from 'react-icons/fa' -import { Popup, PopupTrigger } from '..' -import { Colors, IGlobalProps, Placement, Type, getFontSize, getHeight, isDark , getFormLabelSize } from '../../global' -import { IconButton } from '../IconButton' -import { ListBox } from '../ListBox' -import { IListItemProps, ListItem } from '../ListItem' -import './Dropdown.scss' -import { Tooltip } from '@mui/material' +import React, { useState } from 'react'; +import { FaCaretDown, FaCaretLeft, FaCaretRight, FaCaretUp } from 'react-icons/fa'; +import { Popup, PopupTrigger } from '..'; +import { Colors, IGlobalProps, Placement, Type, getFontSize, getHeight, isDark, getFormLabelSize } from '../../global'; +import { IconButton } from '../IconButton'; +import { ListBox } from '../ListBox'; +import { IListItemProps, ListItem } from '../ListItem'; +import './Dropdown.scss'; +import { Tooltip } from '@mui/material'; export enum DropdownType { - SELECT = "select", - CLICK = "click" + SELECT = 'select', + CLICK = 'click', } export interface IDropdownProps extends IGlobalProps { - items: IListItemProps[] - placement?: Placement - dropdownType: DropdownType - title?: string - closeOnSelect?: boolean; - iconProvider?: (active:boolean, placement?:Placement) => JSX.Element, - selectedVal?: string, - setSelectedVal?: (val: string | number) => unknown, - maxItems?: number, - uppercase?: boolean, - activeChanged?: (isOpen:boolean) => void, - onItemDown?: (e:React.PointerEvent, val:number | string) => boolean, // returns whether to select item + items: IListItemProps[]; + placement?: Placement; + dropdownType: DropdownType; + title?: string; + toolTip?: string; + closeOnSelect?: boolean; + iconProvider?: (active: boolean, placement?: Placement) => JSX.Element; + selectedVal?: string; + setSelectedVal?: (val: string | number, e?: React.MouseEvent) => unknown; + maxItems?: number; + uppercase?: boolean; + activeChanged?: (isOpen: boolean) => void; + onItemDown?: (e: React.PointerEvent, val: number | string) => boolean; // returns whether to select item } /** @@ -37,189 +38,159 @@ export interface IDropdownProps extends IGlobalProps { * Look at: import Select from "react-select"; */ export const Dropdown = (props: IDropdownProps) => { - const { - size, - height, - maxItems, - items, - dropdownType, - selectedVal, - setSelectedVal, - iconProvider, - placement = 'bottom-start', - tooltip, - tooltipPlacement = 'top', - inactive, - color = Colors.MEDIUM_BLUE, - background, - closeOnSelect, - title = "Dropdown", - type, - width, - formLabel, - formLabelPlacement, - fillWidth = true, - onItemDown, - uppercase - } = props + const { + size, + height, + maxItems, + items, + dropdownType, + selectedVal, + toolTip, + setSelectedVal, + iconProvider, + placement = 'bottom-start', + tooltip, + tooltipPlacement = 'top', + inactive, + color = Colors.MEDIUM_BLUE, + background, + closeOnSelect, + title = 'Dropdown', + type, + width, + formLabel, + formLabelPlacement, + fillWidth = true, + onItemDown, + uppercase, + } = props; - const [active, setActive] = useState(false) - const itemsMap = new Map(); - items.forEach((item) => { - itemsMap.set(item.val, item) - }) + const [active, setActive] = useState(false); + const itemsMap = new Map(); + items.forEach(item => { + itemsMap.set(item.val, item); + }); - const getBorderColor = (): Colors | string | undefined => { - switch(type){ - case Type.PRIM: - return undefined; - case Type.SEC: - return color; - case Type.TERT: - if (active) return color; - else return color; - } - } + const getBorderColor = (): Colors | string | undefined => { + switch (type) { + case Type.PRIM: + return undefined; + case Type.SEC: + return color; + case Type.TERT: + if (active) return color; + else return color; + } + }; - const defaultProperties: React.CSSProperties = { - height: getHeight(height, size), - width: fillWidth ? '100%' : width, - fontWeight: 500, - fontSize: getFontSize(size), - fontFamily: 'sans-serif', - textTransform: uppercase ? 'uppercase' : undefined, - borderColor: getBorderColor(), - background, - color: color && background? color : type == (Type.TERT) ? isDark(color) ? Colors.WHITE : Colors.BLACK : color - } + const defaultProperties: React.CSSProperties = { + height: getHeight(height, size), + width: fillWidth ? '100%' : width, + fontWeight: 500, + fontSize: getFontSize(size), + fontFamily: 'sans-serif', + textTransform: uppercase ? 'uppercase' : undefined, + borderColor: getBorderColor(), + background, + color: color && background ? color : type == Type.TERT ? (isDark(color) ? Colors.WHITE : Colors.BLACK) : color, + }; - const backgroundProperties: React.CSSProperties = { - background: background ?? color - } + const backgroundProperties: React.CSSProperties = { + background: background ?? color, + }; - const getCaretDirection = (active: boolean, placement:Placement = 'left'): JSX.Element => { - if (iconProvider) return iconProvider(active, placement); - switch (placement) { - case 'bottom': - if (active) return - return - case 'right': - if (active) return - return - case 'top': - if (active) return - return - default: - if (active) return - return - } - } + const getCaretDirection = (active: boolean, placement: Placement = 'left'): JSX.Element => { + if (iconProvider) return iconProvider(active, placement); + switch (placement) { + case 'bottom': + if (active) return ; + return ; + case 'right': + if (active) return ; + return ; + case 'top': + if (active) return ; + return ; + default: + if (active) return ; + return ; + } + }; - const getToggle = () => { - switch (dropdownType) { - case DropdownType.SELECT: - return ( -
- {selectedVal && ( - - )} -
- { + switch (dropdownType) { + case DropdownType.SELECT: + return ( +
+ {selectedVal && } +
+ +
+
+
+ ); + case DropdownType.CLICK: + default: + return ( +
+ +
+ +
+
+
+ ); + } + }; + + const setActiveChanged = (active: boolean) => { + setActive(active); + props.activeChanged?.(active); + }; + + const dropdown: JSX.Element = ( +
+ + {getToggle()} + + } + placement={placement} + tooltip={tooltip} + tooltipPlacement={tooltipPlacement} + trigger={PopupTrigger.CLICK} + isOpen={active} + setOpen={setActiveChanged} size={size} - icon={getCaretDirection(active,placement)} - color={defaultProperties.color} - inactive - /> -
-
-
- ) - case DropdownType.CLICK: - default: - return ( -
- { + setSelectedVal?.(val, e); + closeOnSelect && setActive(false); + }} + size={size} + /> + } /> -
- -
-
-
- ) - } - } - - const setActiveChanged = (active:boolean) => { - setActive(active); - props.activeChanged?.(active); - } - - const dropdown: JSX.Element = - ( -
- - {getToggle()} - - } - placement={placement} - tooltip={tooltip} - tooltipPlacement={tooltipPlacement} - trigger={PopupTrigger.CLICK} - isOpen={active} - setOpen={setActiveChanged} - size={size} - fillWidth={true} - color={color} - popup={ - { - setSelectedVal?.(val); - closeOnSelect && setActive(false); - }} - size={size} - /> - } - /> -
- ) +
+ ); - return ( - formLabel ? -
-
{formLabel}
- {dropdown} -
- : - dropdown - ) -} + return formLabel ? ( +
+
+ {formLabel} +
+ {dropdown} +
+ ) : ( + dropdown + ); +}; diff --git a/packages/components/src/components/ListBox/ListBox.tsx b/packages/components/src/components/ListBox/ListBox.tsx index abdfd38f3..aa5eb6b44 100644 --- a/packages/components/src/components/ListBox/ListBox.tsx +++ b/packages/components/src/components/ListBox/ListBox.tsx @@ -1,15 +1,15 @@ -import React, { ReactText } from 'react' -import { IListItemProps, ListItem } from '../ListItem' -import './ListBox.scss' -import { Colors, IGlobalProps, isDark , getFormLabelSize } from '../../global' +import React from 'react'; +import { IListItemProps, ListItem } from '../ListItem'; +import './ListBox.scss'; +import { Colors, IGlobalProps } from '../../global'; export interface IListBoxProps extends IGlobalProps { - items: IListItemProps[] - filter?: string - selectedVal?: string | number - setSelectedVal?: (val: string | number) => unknown - maxItems?: number - onItemDown?: (e:React.PointerEvent, val:number|string) => void + items: IListItemProps[]; + filter?: string; + selectedVal?: string | number; + setSelectedVal?: (val: string | number, e?: React.MouseEvent) => unknown; + maxItems?: number; + onItemDown?: (e: React.PointerEvent, val: number | string) => void; } /** @@ -21,56 +21,24 @@ export interface IListBoxProps extends IGlobalProps { * Look at: import Select from "react-select"; */ export const ListBox = (props: IListBoxProps) => { - const { - items, - selectedVal, - setSelectedVal, - filter, - onItemDown, - color = Colors.MEDIUM_BLUE - } = props + const { items, selectedVal, setSelectedVal, filter, onItemDown, color = Colors.MEDIUM_BLUE } = props; - const getListItem = ( - item: IListItemProps, - ind: number, - selected: boolean - ): JSX.Element => { + const getListItem = (item: IListItemProps, ind: number, selected: boolean): JSX.Element => { + return ; + }; + const itemElements: JSX.Element[] = []; + items.forEach((item, ind) => { + if (filter) { + if (filter.toLowerCase() === item.text?.substring(0, filter.length).toLowerCase()) { + itemElements.push(getListItem(item, ind, item.val === selectedVal)); + } + } else { + itemElements.push(getListItem(item, ind, item.val === selectedVal)); + } + }); return ( - - ) - } - let itemElements: JSX.Element[] = [] - items.forEach((item, ind) => { - if (filter) { - if ( - filter.toLowerCase() === - item.text?.substring(0, filter.length).toLowerCase() - ) { - itemElements.push( - getListItem(item, ind, item.val === selectedVal) - ) - } - } else { - itemElements.push( - getListItem(item, ind, item.val === selectedVal) - ) - } - }) - return ( -
- {itemElements} -
- ) -} +
+ {itemElements} +
+ ); +}; diff --git a/packages/components/src/components/ListItem/ListItem.tsx b/packages/components/src/components/ListItem/ListItem.tsx index d76c84b3e..e04c6fbee 100644 --- a/packages/components/src/components/ListItem/ListItem.tsx +++ b/packages/components/src/components/ListItem/ListItem.tsx @@ -1,25 +1,25 @@ -import React, { useState } from 'react' -import * as fa from 'react-icons/fa' -import { getFontSize, IGlobalProps, Type , getFormLabelSize, getHeight } from '../../global' -import { Size } from '../../global/globalEnums' -import { IconButton } from '../IconButton' -import { ListBox } from '../ListBox' -import { Popup, PopupTrigger } from '../Popup' -import './ListItem.scss' +import React, { useState } from 'react'; +import * as fa from 'react-icons/fa'; +import { getFontSize, IGlobalProps, Type, getHeight } from '../../global'; +import { Size } from '../../global/globalEnums'; +import { IconButton } from '../IconButton'; +import { ListBox } from '../ListBox'; +import { Popup, PopupTrigger } from '../Popup'; +import './ListItem.scss'; export interface IListItemProps extends IGlobalProps { - ind?: number - text?: string - val: string | number - icon?: JSX.Element - description?: string - shortcut?: string - items?: IListItemProps[] - selected?: boolean - setSelectedVal?: (val: string | number) => unknown - onClick?: () => void - onItemDown?: (e:React.PointerEvent, val:string| number) => void - uppercase?: boolean + ind?: number; + text?: string; + val: string | number; + icon?: JSX.Element; + description?: string; + shortcut?: string; + items?: IListItemProps[]; + selected?: boolean; + setSelectedVal?: (val: string | number, e?: React.MouseEvent) => unknown; + onClick?: () => void; + onItemDown?: (e: React.PointerEvent, val: string | number) => void; + uppercase?: boolean; } /** @@ -31,104 +31,67 @@ export interface IListItemProps extends IGlobalProps { * Look at: import Select from "react-select"; */ export const ListItem = (props: IListItemProps) => { - const { - ind, - val, - description, - text, - shortcut, - items, - icon, - selected, - setSelectedVal, - onClick, - onItemDown, - inactive, - size = Size.SMALL, - style, - color, - background, - uppercase - } = props + const { val, description, text, shortcut, items, icon, selected, setSelectedVal, onClick, onItemDown, inactive, size = Size.SMALL, style, color, background, uppercase } = props; - const [isHovered, setIsHovered] = useState(false); + const [isHovered, setIsHovered] = useState(false); - let listItem:JSX.Element = ( -
onItemDown?.(e, val) && setSelectedVal?.(val)} - onClick={(e: React.MouseEvent) => { - if (!items) { - !inactive && onClick?.() - !inactive && onClick && e.stopPropagation() - setSelectedVal?.(val) - } - }} - style={{ - minHeight: getHeight(undefined, size), - userSelect: 'none', - ...style - }} - onPointerEnter={() => { - setIsHovered(true) - }} - onPointerLeave={() => { - setIsHovered(false) - }} - > -
-
- {icon} -
{text}
+ const listItem: JSX.Element = ( +
onItemDown?.(e, val) && setSelectedVal?.(val, e)} + onClick={(e: React.MouseEvent) => { + if (!items) { + !inactive && onClick?.(); + !inactive && onClick && e.stopPropagation(); + setSelectedVal?.(val, e); + } + }} + style={{ + minHeight: getHeight(undefined, size), + userSelect: 'none', + ...style, + }} + onPointerEnter={() => { + setIsHovered(true); + }} + onPointerLeave={() => { + setIsHovered(false); + }}> +
+
+ {icon} +
+ {text} +
+
+ {shortcut && !inactive && ( +
+ {shortcut} +
+ )} + {items && !inactive && } color={style?.color ? style.color : color} background={background} inactive />} +
+ {description && !inactive &&
{description}
} +
- {shortcut && !inactive && ( -
- {shortcut} -
- )} - {items && !inactive && ( - } - color={style?.color ? style.color : color} - background={background} - inactive - /> - )} -
- {description && !inactive && ( -
{description}
- )} -
-
-) + ); - if (items && !inactive) return - } - fillWidth={true} - /> - else return <>{listItem} -} + if (items && !inactive) return } fillWidth={true} />; + else return <>{listItem}; +}; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b41fd09dc..7c36a82f2 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -694,10 +694,8 @@ pie title Minerals in my tap water { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Tags", icon:"bolt", toolTip:"Sort by document's tags", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"tag", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - { title: "Pile", icon:"layer-group", toolTip:"View the cards as a pile in the free form view", btnType: ButtonType.ClickButton, expertMode: false, toolType:"pile", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Chat Popup",icon:"lightbulb", toolTip:"Toggle the chat popup's visibility", width: 45, btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-chat",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, { title: "Show Tags", icon:"id-card", toolTip:"Toggle tag annotation panel", width: 45, btnType: ButtonType.ToggleButton, expertMode: false, toolType:"toggle-tags",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, - { title: "Sort", icon: "sort" , toolTip: "Manage sort order / lock status", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true, subMenu: [ { title: "Ascending", toolTip: "Sort the cards in ascending order", btnType: ButtonType.ToggleButton, icon: "sort-up", toolType:"up", ignoreClick: true, scripts: {onClick: '{ return showFreeform(this.toolType, _readOnly_);}'} }, @@ -814,7 +812,7 @@ pie title Minerals in my tap water CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.Tree, CollectionViewType.Time, ]), - title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, _readOnly_); }'}}, + title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, shiftKey, _readOnly_); }'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} }, { title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} }, diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index d9ff21035..15683ebf2 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -16,6 +16,7 @@ import { RegionHelpers } from './Region'; import './Timeline.scss'; import { TimelineOverview } from './TimelineOverview'; import { Track } from './Track'; +import { Id } from '../../../fields/FieldSymbols'; /** * Timeline class controls most of timeline functions besides individual region and track mechanism. Main functions are @@ -56,7 +57,7 @@ export class Timeline extends ObservableReactComponent { private DEFAULT_CONTAINER_HEIGHT: number = 330; private MIN_CONTAINER_HEIGHT: number = 205; - constructor(props: any) { + constructor(props: FieldViewProps) { super(props); makeObservable(this); } @@ -89,7 +90,7 @@ export class Timeline extends ObservableReactComponent { */ @computed private get children(): Doc[] { - const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Document.type) as any); + const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this._props.Document.type) as unknown as DocumentType); if (annotatedDoc) { return DocListCast(this._props.Document[Doc.LayoutFieldKey(this._props.Document) + '_annotations']); } @@ -272,9 +273,9 @@ export class Timeline extends ObservableReactComponent { * for displaying time to standard min:sec */ @action - toReadTime = (time: number): string => { - time = time / 1000; - const inSeconds = Math.round(time * 100) / 100; + toReadTime = (timeIn: number): string => { + const timeSecs = timeIn / 1000; + const inSeconds = Math.round(timeSecs * 100) / 100; const min = Math.floor(inSeconds / 60); const sec = Math.round((inSeconds % 60) * 100) / 100; @@ -552,6 +553,7 @@ export class Timeline extends ObservableReactComponent {
{[...this.children, this._props.Document].map(doc => ( this.mapOfTracks.push(ref)} timeline={this} animatedDoc={doc} @@ -570,7 +572,7 @@ export class Timeline extends ObservableReactComponent {
Current: {this.getCurrentTime()}
{[...this.children, this._props.Document].map(doc => ( -
Doc.BrushDoc(doc)} onPointerOut={() => Doc.UnBrushDoc(doc)}> +
Doc.BrushDoc(doc)} onPointerOut={() => Doc.UnBrushDoc(doc)}>

{StrCast(doc.title)}

))} diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 800eb4914..1bca68846 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import * as CSS from 'csstype'; import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { Animation, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -314,6 +314,7 @@ export class CollectionCardView extends CollectionSubView() { 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()]; + case cardSortings.Tag: return [StrListCast(docA.tags).join(""), StrListCast(docB.tags).join("")]; } })(); //prettier-ignore return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1); diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 1738802b7..542417531 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -48,10 +48,20 @@ ScriptingGlobals.add(function IsNoneSelected() { // toggle: Set overlay status of selected document // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function setView(view: string, getSelected: boolean) { - if (getSelected) return DocumentView.SelectedDocs(); - const selected = DocumentView.SelectedDocs().lastElement(); - selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed'); +ScriptingGlobals.add(function setView(view: string, shiftKey: boolean, checkResult?: boolean) { + if (checkResult) return DocumentView.SelectedDocs(); + const selected = DocumentView.Selected().lastElement(); + if (selected) { + if (shiftKey) { + const newCol = Doc.MakeEmbedding(selected.Document); + newCol._type_collection = view; + selected._props.addDocTab?.(newCol, OpenWhere.addRight); + } else { + selected.Document._type_collection = view; + } + } else { + console.log('[FontIconBox.tsx] changeView failed'); + } return undefined; }); @@ -141,7 +151,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function showFreeform( - attr: 'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'toggle-tags' | 'tag', + attr: 'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'toggle-chat' | 'toggle-tags' | 'tag', checkResult?: boolean, persist?: boolean ) { @@ -152,7 +162,7 @@ ScriptingGlobals.add(function showFreeform( } // prettier-ignore - const map: Map<'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'toggle-tags' | 'tag', + const map: Map<'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down'| 'toggle-chat' | 'toggle-tags' | 'tag', { waitForRender?: boolean; checkResult: (doc: Doc) => boolean; @@ -242,20 +252,6 @@ ScriptingGlobals.add(function showFreeform( doc.showChildTags = !doc.showChildTags; }, }], - ['pile', { - checkResult: (doc: Doc) => doc._type_collection == CollectionViewType.Freeform, - setDoc: (doc: Doc, dv: DocumentView) => { - 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)) - }); - dv._props.addDocTab?.(newCol, OpenWhere.addRight); - }, - }], ]); if (checkResult) { diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 60b2a7519..f58862028 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -160,6 +160,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { buttonList, jsx: undefined, selectedVal: script(), + toolTip: 'Set text font', getStyle: (val: string) => ({ fontFamily: val }), }; }; @@ -174,6 +175,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { buttonList: buttonList.filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value as CollectionViewType)), getStyle: undefined, selectedVal: StrCast(selected[0]._type_collection), + toolTip: 'change view type (press Shift to add as a new view)', } : { jsx: selected.length ? ( @@ -205,11 +207,11 @@ export class FontIconBox extends ViewBoxBaseComponent() { @computed get dropdownListButton() { const script = ScriptCast(this.Document.script); const selectedFunc = () => script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string; - const { buttonList, selectedVal, getStyle, jsx } = (() => { + const { buttonList, selectedVal, getStyle, jsx, toolTip } = (() => { switch (this.Document.title) { case 'Font': return this.handleFontDropdown(selectedFunc, this.buttonList); case 'Perspective': return this.handleViewDropdown(script, this.buttonList); - default: return { buttonList: this.buttonList, selectedVal: selectedFunc(), jsx: undefined, getStyle: undefined }; + default: return { buttonList: this.buttonList, selectedVal: selectedFunc(), toolTip: undefined, jsx: undefined, getStyle: undefined }; } // prettier-ignore })(); if (jsx) return jsx; @@ -225,9 +227,10 @@ export class FontIconBox extends ViewBoxBaseComponent() { return ( script.script.run({ this: this.Document, value }), `dropdown select ${this.label}`)} + setSelectedVal={undoable((value, e) => script.script.run({ this: this.Document, value, shiftKey: e.shiftKey }), `dropdown select ${this.label}`)} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} + toolTip={toolTip} type={Type.TERT} closeOnSelect={false} dropdownType={DropdownType.SELECT} -- cgit v1.2.3-70-g09d2 From a496c583f375fe5e118f786b1244c42bc8a34fec Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Jan 2025 14:20:40 -0500 Subject: fixed flashcard practice mode. moved doc sorting from cardDeck into collectionSubView to apply it to all collections. --- .../src/components/Dropdown/Dropdown.tsx | 30 ++---- src/client/util/CurrentUserUtils.ts | 10 +- .../views/collections/CollectionCardDeckView.tsx | 119 ++++----------------- src/client/views/collections/CollectionSubView.tsx | 49 ++++++++- .../views/collections/FlashcardPracticeUI.tsx | 4 +- src/client/views/global/globalScripts.ts | 35 +++--- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/client/views/nodes/imageEditor/ImageEditor.tsx | 69 ++---------- .../views/nodes/imageEditor/ImageEditorButtons.tsx | 1 + 9 files changed, 108 insertions(+), 211 deletions(-) (limited to 'src/client/views/collections/CollectionCardDeckView.tsx') diff --git a/packages/components/src/components/Dropdown/Dropdown.tsx b/packages/components/src/components/Dropdown/Dropdown.tsx index 0953f412c..b9b6f01b8 100644 --- a/packages/components/src/components/Dropdown/Dropdown.tsx +++ b/packages/components/src/components/Dropdown/Dropdown.tsx @@ -99,22 +99,14 @@ export const Dropdown = (props: IDropdownProps) => { background: background ?? color, }; - const getCaretDirection = (active: boolean, placement: Placement = 'left'): JSX.Element => { - if (iconProvider) return iconProvider(active, placement); - switch (placement) { - case 'bottom': - if (active) return ; - return ; - case 'right': - if (active) return ; - return ; - case 'top': - if (active) return ; - return ; - default: - if (active) return ; - return ; - } + const getCaretDirection = (isActive: boolean, caretPlacement: Placement = 'left'): JSX.Element => { + if (iconProvider) return iconProvider(isActive, caretPlacement); + switch (caretPlacement) { + default: + case 'bottom':return isActive ? : ; + case 'right': return isActive ? : ; + case 'top': return isActive ? : ; + } // prettier-ignore }; const getToggle = () => { @@ -143,9 +135,9 @@ export const Dropdown = (props: IDropdownProps) => { } }; - const setActiveChanged = (active: boolean) => { - setActive(active); - props.activeChanged?.(active); + const setActiveChanged = (isActive: boolean) => { + setActive(isActive); + props.activeChanged?.(isActive); }; const dropdown: JSX.Element = ( diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7c36a82f2..e806675aa 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -688,7 +688,7 @@ pie title Minerals in my tap water { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"hcenter", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } - static cardTools(): Button[] { + static sortTools(): Button[] { return [ { title: "Time", icon:"hourglass-half", toolTip:"Sort by most recent document creation", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"time", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, @@ -704,7 +704,7 @@ pie title Minerals in my tap water ] } - static tagGroupTools(): Button[] { + static filterTools(): Button[] { const defaultTagButtonDescs = [ { title: "Star", isSystem: false,icon: "star", toolTip:"Click to toggle visibility of Star tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#star", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, { title: "Like", isSystem: false,icon: "heart", toolTip:"Click to toggle visibility of Like tagged Docs", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"#like", funcs: {}, scripts: { onClick: '{ return setTagFilter(this.toolType, _added_, _readOnly_);}'}}, @@ -723,7 +723,6 @@ pie title Minerals in my tap water { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Fit All", icon: "object-group", toolTip: "Fit Docs to View (double click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } static textTools():Button[] { @@ -822,15 +821,14 @@ pie title Minerals in my tap water { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, - { title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.tagGroupTools(),ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string}, + { title: "Filter", icon: "=", toolTip: "Filter cards by tags", subMenu: CurrentUserUtils.filterTools(), ignoreClick:true, toolType:DocumentType.COL, funcs: {hidden: '!SelectedDocType(this.toolType, this.expertMode)'}, btnType: ButtonType.MultiToggleButton, width: 30, backgroundColor: doc.userVariantColor as string}, + { title: "Sort", icon: "Sort", toolTip: "Sort Documents", subMenu: CurrentUserUtils.sortTools(), expertMode: false, toolType:DocumentType.COL, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available - { title: "Card", icon: "Card", toolTip: "Card View Tools", subMenu: CurrentUserUtils.cardTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available - // { title: "Create", icon: "Create", toolTip: "Assign card labels", subMenu: CurrentUserUtils.labelTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 1bca68846..aaf33c3a1 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,16 +1,16 @@ -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; import * as CSS from 'csstype'; -import { ClientUtils, DashColor, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; +import { ClientUtils, imageUrlToBase64, returnFalse, returnNever, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; 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 } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types'; import { URLField } from '../../../fields/URLField'; import { gptImageLabel } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -25,21 +25,12 @@ 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 { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { SettingsManager } from '../../util/SettingsManager'; -enum cardSortings { - Time = 'time', - Type = 'type', - Color = 'color', - Chat = 'chat', - Tag = 'tag', - None = '', -} - /** * New view type specifically for studying more dynamically. Allows you to reorder docs however you see fit, easily * sort and filter using presets, and customize your experience with chat gpt. @@ -60,7 +51,6 @@ export class CollectionCardView extends CollectionSubView() { @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap(); - @observable _docDraggedIndex: number = -1; @observable _cursor: CSS.Property.Cursor = 'ew-resize'; constructor(props: SubCollectionViewProps) { @@ -102,18 +92,8 @@ export class CollectionCardView extends CollectionSubView() { componentDidMount() { this._props.setContentViewBox?.(this); - this._disposers.sort = reaction( - () => GPTPopup.Instance.visible, - isVis => { - if (isVis) { - this.openChatPopup(); - } else { - this.Document.card_sort = this.cardSort === cardSortings.Chat ? '' : this.Document.card_sort; - } - } - ); // if card deck moves, then the child doc views are hidden so their screen to local transforms will return empty rectangles - // when inquired from the dom (below in childScreenToLocal). When the doc is actually renders, we need to act like the + // when inquired from the dom (below in childScreenToLocal). When the doc is actually rendered, we need to act like the // dash data just changed and trigger a React involidation with the correct data (read from the dom). this._disposers.child = reaction( () => [this.Document.x, this.Document.y], @@ -136,14 +116,11 @@ export class CollectionCardView extends CollectionSubView() { this._dropDisposer?.(); } - @computed get cardSort() { - return StrCast(this.Document.card_sort) as cardSortings; - } /** * Number of rows of cards to be rendered */ @computed get numRows() { - return Math.ceil(this.sortedDocs.length / this._maxRowCount); + return Math.ceil(this.childDocs.length / this._maxRowCount); } /** * Circle arc size, in radians, to layout cards @@ -211,7 +188,7 @@ export class CollectionCardView extends CollectionSubView() { * @returns the card's new index */ findCardDropIndex = (mouseX: number, mouseY: number) => { - const cardCount = this.sortedDocs.length; + const cardCount = this.childDocs.length; let index = 0; const cardWidth = cardCount < this._maxRowCount ? this._props.PanelWidth() / cardCount : this._props.PanelWidth() / this._maxRowCount; @@ -245,8 +222,8 @@ export class CollectionCardView extends CollectionSubView() { */ @action onPointerMove = (x: number, y: number) => { - if (DragManager.docsBeingDragged.some(doc => this.sortedDocs.includes(doc)) || SnappingManager.CanEmbed) { - this._docDraggedIndex = this.findCardDropIndex(x, y); + if (DragManager.docsBeingDragged.some(doc => this.childDocs.includes(doc)) || SnappingManager.CanEmbed) { + this.docDraggedIndex = this.findCardDropIndex(x, y); } }; @@ -259,11 +236,11 @@ export class CollectionCardView extends CollectionSubView() { onInternalDrop = undoable( action((e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { - const dragIndex = this._docDraggedIndex; + const dragIndex = this.docDraggedIndex; const draggedDoc = DragManager.docsBeingDragged[0]; if (dragIndex > -1 && draggedDoc) { - this._docDraggedIndex = -1; - const sorted = this.sortedDocs; + this.docDraggedIndex = -1; + const sorted = this.childDocs; const originalIndex = sorted.findIndex(doc => doc === draggedDoc); this.Document.card_sort = ''; @@ -295,50 +272,6 @@ export class CollectionCardView extends CollectionSubView() { .map(({ i }) => i) .join('.'); - /** - * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the - * front, latter cards to the back - * @param docs - * @param sortType - * @param isDesc - * @returns - */ - sort = (docsIn: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => { - const docs = docsIn.slice(); // need make new object list since sort() modifies the incoming list which confuses mobx caching - sortType && - docs.sort((docA, docB) => { - const [typeA, typeB] = (() => { - switch (sortType) { - 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()]; - case cardSortings.Tag: return [StrListCast(docA.tags).join(""), StrListCast(docB.tags).join("")]; - } - })(); //prettier-ignore - return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1); - }); - if (dragIndex !== -1) { - const draggedDoc = DragManager.docsBeingDragged[0]; - const originalIndex = docs.findIndex(doc => doc === draggedDoc); - - originalIndex !== -1 && docs.splice(originalIndex, 1); - draggedDoc && docs.splice(dragIndex, 0, draggedDoc); - } - - return docs; - }; - - @computed get sortedDocs() { - return this.sort( - this.childCards.map(card => card.layout), - this.cardSort, - BoolCast(this.Document.card_sort_isDesc), - this._docDraggedIndex - ); - } - isChildContentActive = computedFn( (doc: Doc) => () => this._props.isContentActive?.() === false @@ -560,16 +493,6 @@ export class CollectionCardView extends CollectionSubView() { }); }, ''); - /** - * Opens up the chat popup and starts the process for smart sorting. - */ - openChatPopup = async () => { - GPTPopup.Instance.setVisible(true); - GPTPopup.Instance.setMode(GPTPopupMode.CARD); - GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded - await this.childPairStringListAndUpdateSortDesc(); - }; - childScreenToLocal = computedFn((doc: Doc, index: number, isSelected: boolean) => () => { // need to explicitly trigger an invalidation since we're reading everything from the Dom this._forceChildXf; @@ -615,7 +538,7 @@ export class CollectionCardView extends CollectionSubView() { setupMoveUpEvents( this, e, - (e: PointerEvent, down: number[], delta: number[]) => { + (emove: PointerEvent, down: number[], delta: number[]) => { this.layoutDoc._cardWidth = Math.max(10, delta[0] < 0 ? Math.floor(this.cardWidth + delta[0]) : Math.ceil(this.cardWidth + delta[0])); return false; }, @@ -662,9 +585,9 @@ export class CollectionCardView extends CollectionSubView() { * Actually renders all the cards */ @computed get renderCards() { - trace(); + console.log(this.docDraggedIndex, this.childDocs[0].title, this.childDocs[1].title); // Map sorted documents to their rendered components - return this.sortedDocs.map((doc, index) => { + return this.childDocs.map((doc, index) => { const cardsInRow = this.cardsInRowThatIncludesCardIndex(index); const childScreenToLocal = this.childScreenToLocal(doc, index, doc === this.curDoc()); @@ -674,7 +597,7 @@ export class CollectionCardView extends CollectionSubView() { 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()), (this._props.PanelHeight() - 80) / (aspect * (this._props.PanelWidth() / 10)))); // prettier-ignore - const hscale = Math.min(this.sortedDocs.length, this._maxRowCount) / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size + const hscale = Math.min(this.childDocs.length, this._maxRowCount) / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size return (
DocCast(this.layoutDoc._card_curDoc); render() { - trace(); const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale; return (
this.createDashEventsTarget(ele)} - onPointerDown={action(e => { - if (e.button === 2 || e.ctrlKey) return; - this.releaseCurDoc(); - })} - onPointerLeave={action(() => (this._docDraggedIndex = -1))} + onPointerDown={e => e.button !== 2 && !e.ctrlKey && this.releaseCurDoc()} + onPointerLeave={action(() => (this.docDraggedIndex = -1))} onPointerMove={e => this.onPointerMove(...this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY))} onDrop={this.onExternalDrop.bind(this)} style={{ diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 0c059f729..5e99bec39 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,7 +1,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; import * as React from 'react'; import * as rp from 'request-promise'; -import { ClientUtils, returnFalse } from '../../../ClientUtils'; +import { ClientUtils, DashColor, returnFalse } from '../../../ClientUtils'; import CursorField from '../../../fields/CursorField'; import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc'; import { AclPrivate, DocData } from '../../../fields/DocSymbols'; @@ -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, toList } from '../../../fields/Types'; +import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; @@ -29,6 +29,14 @@ import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FlashcardPracticeUI } from './FlashcardPracticeUI'; import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; +export enum docSortings { + Time = 'time', + Type = 'type', + Color = 'color', + Chat = 'chat', + Tag = 'tag', + None = '', +} export interface CollectionViewProps extends React.PropsWithChildren { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) @@ -150,6 +158,8 @@ export function CollectionSubView() { unrecursiveDocFilters = () => [...(this._props.childFilters?.().filter(f => !ClientUtils.IsRecursiveFilter(f)) || [])]; childDocRangeFilters = () => [...(this._props.childFiltersByRanges?.() || []), ...this.collectionRangeDocFilters()]; searchFilterDocs = () => this._props.searchFilterDocs?.() ?? DocListCast(this.Document._searchFilterDocs); + + @observable docDraggedIndex = -1; @computed.struct get childDocs() { TraceMobx(); let rawdocs: (Doc | Promise)[] = []; @@ -166,8 +176,10 @@ export function CollectionSubView() { const templateRoot = this._props.TemplateDataDocument; rawdocs = templateRoot && !this._props.isAnnotationOverlay ? [Doc.GetProto(templateRoot)] : []; } - - const childDocs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc); + const childDocs = this.childSortedDocs( + rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this._props.ignoreUnrendered || !d.layout_unrendered)).map(d => d as Doc), + this.docDraggedIndex + ); const childDocFilters = this.childDocFilters(); const childFiltersByRanges = this.childDocRangeFilters(); @@ -214,6 +226,35 @@ export function CollectionSubView() { return docsforFilter; } + childSortedDocs = (docsIn: Doc[], dragIndex: number) => { + const sortType = StrCast(this.Document[this._props.fieldKey + '_sort']) as docSortings; + const isDesc = BoolCast(this.Document[this._props.fieldKey + '_sort_desc']); + const docs = docsIn.slice(); + if (sortType) { + docs.sort((docA, docB) => { + const [typeA, typeB] = (() => { + switch (sortType) { + default: + case docSortings.Type: return [StrCast(docA.type), StrCast(docB.type)]; + case docSortings.Chat: return [NumCast(docA.chatIndex, 9999), NumCast(docB.chatIndex,9999)]; + case docSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; + case docSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()]; + case docSortings.Tag: return [StrListCast(docA.tags).join(""), StrListCast(docB.tags).join("")]; + } + })(); //prettier-ignore + return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1); + }); + } + if (dragIndex !== -1) { + const draggedDoc = DragManager.docsBeingDragged[0]; + const originalIndex = docs.findIndex(doc => doc === draggedDoc); + + originalIndex !== -1 && docs.splice(originalIndex, 1); + draggedDoc && docs.splice(dragIndex, 0, draggedDoc); + } + return docs; + }; + @action protected async setCursorPosition(position: [number, number]) { let ind; diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index 77f1db9ad..c071c5fb8 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -58,7 +58,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent doc.title === 'Filter'); } // prettier-ignore - @computed get practiceMode() { return this._props.allChildDocs().some(doc => doc._flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore + @computed get practiceMode() { return this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale); btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height)); @@ -179,7 +179,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent ); } - tryFilterOut = (doc: Doc) => (this.practiceMode && BoolCast(doc?._flashcardType) && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct + tryFilterOut = (doc: Doc) => (this.practiceMode && doc?._layout_flashcardType && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct render() { return (
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 542417531..b02eabab0 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -40,6 +40,7 @@ import { WebBox } from '../nodes/WebBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import { OpenWhere } from '../nodes/OpenWhere'; +import { docSortings } from '../collections/CollectionSubView'; // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function IsNoneSelected() { @@ -197,43 +198,35 @@ ScriptingGlobals.add(function showFreeform( checkResult: (doc: Doc) => BoolCast(doc?._freeform_useClusters, false), setDoc: (doc: Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; }, }], - ['flashcards', { - checkResult: (doc: Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false), - setDoc: (doc: Doc, dv: DocumentView) => { Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards}, // prettier-ignore - }], ['time', { - checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "time", - setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "time" ? doc.card_sort = '' : doc.card_sort = 'time'}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "time", + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore }], ['docType', { - checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "type", - setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "type" ? doc.card_sort = '' : doc.card_sort = 'type'}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "type", + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore }], ['color', { - checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "color", - setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "color" ? doc.card_sort = '' : doc.card_sort = 'color'}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "color", + setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutFieldKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore }], ['tag', { - checkResult: (doc: Doc) => StrCast(doc?.card_sort) === "tag", - setDoc: (doc: Doc, dv: DocumentView) => { doc.card_sort === "tag" ? doc.card_sort = '' : doc.card_sort = 'tag'}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "tag", + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore }], ['up', { - checkResult: (doc: Doc) => BoolCast(!doc?.card_sort_isDesc), - setDoc: (doc: Doc, dv: DocumentView) => { - doc.card_sort_isDesc = false; - }, + checkResult: (doc: Doc) => BoolCast(!doc?.[Doc.LayoutFieldKey(doc)+"_sort_desc"]), + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = undefined; }, }], ['down', { - checkResult: (doc: Doc) => BoolCast(doc?.card_sort_isDesc), - setDoc: (doc: Doc, dv: DocumentView) => { - doc.card_sort_isDesc = true; - }, + checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort_desc"]), + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_desc"] = true; }, }], ['toggle-chat', { checkResult: (doc: Doc) => GPTPopup.Instance.visible, setDoc: (doc: Doc, dv: DocumentView) => { if (GPTPopup.Instance.visible){ - doc.card_sort = '' + doc[Doc.LayoutFieldKey(doc)+"_sort"] = ''; GPTPopup.Instance.setVisible(false); } else { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index e0c360132..f5291a4c1 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -1,7 +1,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import axios from 'axios'; -import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; +import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import ReactLoading from 'react-loading'; diff --git a/src/client/views/nodes/imageEditor/ImageEditor.tsx b/src/client/views/nodes/imageEditor/ImageEditor.tsx index 2ae6ee1dd..6b1d05031 100644 --- a/src/client/views/nodes/imageEditor/ImageEditor.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditor.tsx @@ -551,8 +551,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc img.src = src; if (!currImg.current || !originalImg.current || !imageRootDoc) return undefined; try { - const res = await createNewImgDoc(img, false); - return res; + return await createNewImgDoc(img, false); } catch (err) { console.log(err); } @@ -589,9 +588,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc // disable once edited has been clicked (doesn't make sense to change after first edit) disabled={edited} checked={isNewCollection} - onChange={() => { - setIsNewCollection(prev => !prev); - }} + onChange={() => setIsNewCollection(prev => !prev)} /> } label="Create New Collection" @@ -610,49 +607,13 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc return (
-
- {imageEditTools.map(tool => { - return ImageToolButton(tool, tool.type === currTool.type, changeTool); - })} -
+
{imageEditTools.map(tool => ImageToolButton(tool, tool.type === currTool.type, changeTool))}
{currTool.type == ImageToolType.Cut && (
-
)}
e.stopPropagation()}> @@ -669,9 +630,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc defaultValue={genFillTool.sliderDefault} size="small" valueLabelDisplay="auto" - onChange={(e, val) => { - setCursorData(prev => ({ ...prev, width: val as number })); - }} + onChange={(e, val) => setCursorData(prev => ({ ...prev, width: val as number }))} /> )} {currTool.type === ImageToolType.Cut && ( @@ -687,9 +646,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc defaultValue={cutTool.sliderDefault} size="small" valueLabelDisplay="auto" - onChange={(e, val) => { - setCursorData(prev => ({ ...prev, width: val as number })); - }} + onChange={(e, val) => setCursorData(prev => ({ ...prev, width: val as number }))} /> )}
@@ -701,9 +658,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc e.stopPropagation(); handleUndo(); }} - onPointerUp={e => { - e.stopPropagation(); - }} + onPointerUp={e => e.stopPropagation()} color={activeColor} tooltip="Undo" icon={} @@ -714,9 +669,7 @@ const ImageEditor = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc e.stopPropagation(); handleRedo(); }} - onPointerUp={e => { - e.stopPropagation(); - }} + onPointerUp={e => e.stopPropagation()} color={activeColor} tooltip="Redo" icon={} diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx index de2116253..e810881a5 100644 --- a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx +++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx @@ -55,6 +55,7 @@ export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTo return (