From 9b0fa4d5581942c54375c10e3887c8010d3eb5e4 Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Thu, 27 Jun 2024 14:06:22 -0400 Subject: time --- src/client/views/collections/CollectionCarouselView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/collections/CollectionCarouselView.tsx') diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 2adad68e0..4884db709 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -33,7 +33,7 @@ enum practiceVal { export class CollectionCarouselView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore - get starField() { return this.fieldKey + "_star"; } // prettier-ignore + get starField() { return "star"; } // prettier-ignore constructor(props: any) { super(props); -- cgit v1.2.3-70-g09d2 From 4f2ee4a8642a93fb399b979750078374b317af32 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 17 Sep 2024 12:29:48 -0400 Subject: fixed carouselfView to fade. cleaned up code a bit --- .../views/collections/CollectionCarousel3DView.tsx | 1 + .../views/collections/CollectionCarouselView.scss | 15 ++ .../views/collections/CollectionCarouselView.tsx | 223 ++++++++++++--------- src/client/views/collections/TabDocView.tsx | 4 +- 4 files changed, 151 insertions(+), 92 deletions(-) (limited to 'src/client/views/collections/CollectionCarouselView.tsx') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index c799eb3c8..54cc02825 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -78,6 +78,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={undefined} + containerViewPath={this.childContainerViewPath} onDoubleClickScript={this.onChildDoubleClick} renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index f115bb40a..01b20d6d3 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -12,6 +12,21 @@ user-select: none; } } +.collectionCarouselView-addFlashcards { + justify-content: center; + align-items: center; + height: 100%; + z-index: -1; + pointer-events: none; +} +.collectionCarouselView-recentlyMissed { + color: red; + z-index: 999; + position: relative; + left: 10px; + top: 10px; + pointer-events: none; +} .carouselView-back, .carouselView-fwd, .carouselView-star, diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 4bec2d963..5d71177c3 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,12 +1,11 @@ /* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { computed, makeObservable } from 'mobx'; +import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; -import { emptyFunction } from '../../../Utils'; -import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { StopEvent, returnOne, returnZero } from '../../../ClientUtils'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { ContextMenu } from '../ContextMenu'; @@ -32,13 +31,34 @@ export class CollectionCarouselView extends CollectionSubView() { get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore get starField() { return this.fieldKey + "_star"; } // prettier-ignore + _fadeTimer: NodeJS.Timeout | undefined; + _resetter: IReactionDisposer | undefined; + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } + @observable _last_index = this.carouselIndex; + @observable _last_opacity = 1; + + componentDidMount() { + this._resetter = reaction( + // automatically reset practice fields when all cards have been marked as correct + () => this.carouselItems.length, + itemsCount => { + if (this.layoutDoc.filterOp === cardMode.PRACTICE && !itemsCount) { + this.layoutDoc.filterOp = undefined; // if all of the cards are correct, show all cards and exit practice mode + this.carouselItems.forEach(item => { // reset all the practice values + item[this.practiceField] = undefined; + }); + } + } // prettier-ignore + ); + } componentWillUnmount() { this._dropDisposer?.(); + this._resetter?.(); } protected createDashEventsTarget = (ele: HTMLDivElement | null) => { @@ -48,43 +68,24 @@ export class CollectionCarouselView extends CollectionSubView() { } }; + @computed get marginX() { return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore + @computed get carouselIndex() { return NumCast(this.layoutDoc._carousel_index) % this.carouselItems.length; } // prettier-ignore @computed get carouselItems() { - return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK); - } - @computed get marginX() { - return NumCast(this.layoutDoc.caption_xMargin, 50); + return DocListCast(this.childDocList) + .filter(doc => doc.type !== DocumentType.LINK) + .filter(doc => { + switch (StrCast(this.layoutDoc.filterOp)) { + case cardMode.STAR: return !!doc[this.starField]; // show only cards that are starred + case cardMode.PRACTICE: return doc[this.practiceField] !== practiceVal.CORRECT;// show only cards that aren't marked as correct + default: return true; + } // prettier-ignore + }); } - move = (dir: number) => { - const moveToCardWithField = (match: (doc: Doc) => boolean): boolean => { - let startInd = (NumCast(this.layoutDoc._carousel_index) + dir) % this.carouselItems.length; - while (!match(this.carouselItems?.[startInd].layout) && (startInd + dir + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) { - startInd = (startInd + dir + this.carouselItems.length) % this.carouselItems.length; - } - if (match(this.carouselItems?.[startInd].layout)) { - this.layoutDoc._carousel_index = startInd; - return true; - } - return match(this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout); - }; - switch (StrCast(this.layoutDoc.filterOp)) { - case cardMode.STAR: // go to a flashcard that is starred, skip the ones that aren't - if (!moveToCardWithField((doc: Doc) => !!doc[this.starField])) { - this.layoutDoc.filterOp = undefined; // if there aren't any starred, show all cards - } - break; - case cardMode.PRACTICE: // go to a new index that is missed, skip the ones that are correct - if (!moveToCardWithField((doc: Doc) => doc[this.practiceField] !== practiceVal.CORRECT)) { - this.layoutDoc.filterOp = undefined; // if all of the cards are correct, show all cards and exit practice mode - - this.carouselItems.forEach(item => { // reset all the practice values - item.layout[this.practiceField] = undefined; - }); - } - break; - default: moveToCardWithField(returnTrue); - } // prettier-ignore - }; + move = action((dir: number) => { + this._last_index = this.carouselIndex; + this.layoutDoc._carousel_index = (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length; + }); /** * Goes to the next Doc in the stack subject to the currently selected filter option. @@ -107,8 +108,8 @@ export class CollectionCarouselView extends CollectionSubView() { */ star = (e: React.MouseEvent) => { e.stopPropagation(); - const curDoc = this.carouselItems[NumCast(this.layoutDoc._carousel_index)]; - curDoc.layout[this.starField] = curDoc.layout[this.starField] ? undefined : true; + const curDoc = this.carouselItems[this.carouselIndex]; + curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true); }; /* @@ -116,8 +117,8 @@ export class CollectionCarouselView extends CollectionSubView() { */ setPracticeVal = (e: React.MouseEvent, val: string) => { e.stopPropagation(); - const curDoc = this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]; - curDoc.layout[this.practiceField] = val; + const curDoc = this.carouselItems[this.carouselIndex]; + curDoc && (curDoc[this.practiceField] = val); this.advance(e); }; @@ -132,7 +133,6 @@ export class CollectionCarouselView extends CollectionSubView() { captionWidth = () => this._props.PanelWidth() - 2 * this.marginX; specificMenu = (): void => { const cm = ContextMenu.Instance; - const revealOptions = cm.findByDescription('Filter Flashcards'); const revealItems = revealOptions?.subitems ?? []; revealItems.push({description: 'All', event: () => {this.layoutDoc.filterOp = undefined;}, icon: 'layer-group',}); // prettier-ignore @@ -141,32 +141,78 @@ export class CollectionCarouselView extends CollectionSubView() { revealItems.push({description: 'Quiz Cards', event: () => {this.layoutDoc.filterOp = cardMode.QUIZ;}, icon: 'pencil',}); // prettier-ignore !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); }; + + isChildContentActive = () => + this._props.isContentActive?.() === false + ? false + : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) + ? true + : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false + ? false + : undefined; + + renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { + return ( + + ); + }; + /** + * Display an overlay of the previous card that crossfades to the next card + */ + @computed get overlay() { + const fadeTime = 500; + const lastDoc = this.carouselItems?.[this._last_index]; + return !lastDoc || this.carouselIndex === this._last_index ? null : ( +
+ {this.renderDoc( + lastDoc, + false, // hide captions if the carousel is configured to show the captions + action((r: DocumentView | null) => { + if (r) { + this._fadeTimer && clearTimeout(this._fadeTimer); + this._last_opacity = 0; + this._fadeTimer = setTimeout( + action(() => { + this._last_index = -1; + this._last_opacity = 1; + }), + fadeTime + ); + } + }) + )} +
+ ); + } @computed get content() { - const index = NumCast(this.layoutDoc._carousel_index); + const index = this.carouselIndex; const curDoc = this.carouselItems?.[index]; const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined }; const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption); - return !(curDoc?.layout instanceof Doc) ? null : ( + return !curDoc ? null : ( <>
- + {this.renderDoc(curDoc, !!carouselShowsCaptions)} + {this.overlay}
{!carouselShowsCaptions ? null : (
- +
)} ); } @computed get buttons() { - if (!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]) return null; + if (!this.carouselItems?.[this.carouselIndex]) return null; return ( <>
@@ -196,7 +242,7 @@ export class CollectionCarouselView extends CollectionSubView() {
- +
this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}> @@ -208,6 +254,24 @@ export class CollectionCarouselView extends CollectionSubView() { ); } + /** + * Prompts user to add more flashcaards if they are in practice mode but there are no flashcards + */ + renderAddFlashcards = () =>

+ Add flashcards! +

// prettier-ignore + + /** + * Displays message that a flashcard was recently missed if it had previously been marked as wrong. + * */ + renderRecentlyMissed = () =>

+ Recently missed! +

// prettier-ignore + render() { return (
{this.content} - {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */} -

- Add flashcards! -

- {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */} -

- Recently missed! -

+ {this.renderAddFlashcards()} + {this.renderRecentlyMissed()} {this.Document._chromeHidden ? null : this.buttons}
); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index def1ea731..f56ea9d76 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -506,13 +506,13 @@ export class TabDocView extends ObservableReactComponent { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - onActiveContentItemChanged = (contentItem: any) => { + onActiveContentItemChanged = action((contentItem: any) => { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false)); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } - }; + }); // adds a tab to the layout based on the locaiton parameter which can be: // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab, -- cgit v1.2.3-70-g09d2 From d95730d904612640184ca6fdc00864b0c81b0c0c Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 18 Sep 2024 20:46:38 -0400 Subject: lots of changes to fix dragging cards, integrate iconTags with other tags, sizing docs when selected to fit window, --- eslint.config.mjs | 1 + src/client/util/CurrentUserUtils.ts | 14 +- src/client/util/DragManager.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 44 ++---- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/StyleProvider.tsx | 4 - src/client/views/TagsView.tsx | 20 +-- .../views/collections/CollectionCardDeckView.scss | 8 +- .../views/collections/CollectionCardDeckView.tsx | 152 ++++++++++++--------- .../views/collections/CollectionCarouselView.tsx | 1 - .../views/collections/CollectionStackingView.tsx | 3 - src/client/views/collections/CollectionSubView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 5 + src/client/views/nodes/IconTagBox.scss | 24 ++-- src/client/views/nodes/IconTagBox.tsx | 106 +++++++------- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 + src/client/views/search/FaceRecognitionHandler.tsx | 3 +- 18 files changed, 198 insertions(+), 204 deletions(-) (limited to 'src/client/views/collections/CollectionCarouselView.tsx') diff --git a/eslint.config.mjs b/eslint.config.mjs index 8926afd7c..f7063caa5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -36,6 +36,7 @@ export default [ 'no-multi-assign': 'off', 'no-underscore-dangle': 'off', 'no-nested-ternary': 'off', + 'no-param-reassign': 'error', 'lines-between-class-members': 'off', 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'warn', diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index eee6be937..262916312 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -2,7 +2,7 @@ import { reaction, runInAction } from "mobx"; import * as rp from 'request-promise'; import { ClientUtils, OmitKeys } from "../../ClientUtils"; -import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, FieldType, Opt, StrListCast } from "../../fields/Doc"; import { DocData } from "../../fields/DocSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -688,18 +688,18 @@ pie title Minerals in my tap water static tagGroupTools(): Button[] { if (!Doc.UserDoc().activeDashboard) { - Doc.UserDoc().myFilterHotKeyTitles = new List(['Star', 'Heart', 'Bolt', 'Cloud']); + Doc.UserDoc().myFilterHotKeyTitles = new List(['star', 'heart', 'bolt', 'cloud']); - ['Star', 'Heart', 'Bolt', 'Cloud'].forEach(key => { + StrListCast(Doc.UserDoc().myFilterHotKeyTitles).forEach(key => { Doc.UserDoc()[key] = key.toLowerCase(); }); } // hack: if there's no dashboard, create default filters. otherwise, just make sure that the Options button is preserved return (Doc.UserDoc().activeDashboard ? [] : [ - { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "star", isSystem: false,icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, ]).concat([ { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton, expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}} ]) diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index d55d193cc..81ea840f1 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -29,7 +29,7 @@ import { dropActionType } from './DropActionTypes'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-require-imports const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore /** @@ -101,7 +101,6 @@ export namespace DragManager { // event called when the drag operation results in a drop action export class DropEvent { - // eslint-disable-next-line no-useless-constructor constructor( readonly x: number, readonly y: number, diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 87dd5f45a..32bf67df1 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FaEdit } from 'react-icons/fa'; import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc } from '../../fields/Doc'; +import { Doc, DocListCast } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; @@ -28,7 +28,6 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; import { OpenWhere } from './nodes/OpenWhere'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; -import { DocData } from '../../fields/DocSymbols'; @observer export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> { @@ -287,44 +286,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( get keywordButton() { const targetDoc = this.view0?.Document; - const metaBtn = (name: string, icon: IconProp) => { - const tooltip = `Toggle ${name}`; - return ( - {tooltip}
}> -
- { - if (name === 'tags') { - targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags); - } else { - targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels); - } - }} - /> -
- - ); - }; - return !targetDoc ? null : (
-
+ {/*
{metaBtn('tags', 'star')} {metaBtn('keywords', 'id-card')} -
+
*/} Open keyword menu
}>
{ - // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags; - }}> + onClick={undoable(e => { + const showing = DocumentView.Selected().some(dv => dv.showTags); + DocumentView.Selected().forEach(dv => { + dv.layoutDoc._layout_showTags = !showing; + if (e.shiftKey) + DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => { + if (doc.face) doc.hidden = showing; + }); + }); + }, 'show Doc tags')}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 5e7908725..2ba96c2a9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -837,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, + top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} /> @@ -846,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent {DocumentView.Selected().length > 1 ? : null} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index eb434db40..3545afcee 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -9,7 +9,6 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; import { FaFilter } from 'react-icons/fa'; import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; @@ -22,7 +21,6 @@ import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; -import { IconTagBox } from './nodes/IconTagBox'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; import { TagsView } from './TagsView'; @@ -366,7 +364,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt docView?.() ? : null; - const iconTags = () => doc?.[DocData].showIconTags ? : null; return ( <> @@ -375,7 +372,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); } diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index be2c28185..a678d5580 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent'; import './TagsView.scss'; import { DocumentView } from './nodes/DocumentView'; import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; +import { IconTagBox } from './nodes/IconTagBox'; /** * The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection. @@ -59,7 +60,7 @@ export class TagItem extends ObservableReactComponent { * @param tag tag string * @returns tag collection Doc or undefined */ - public static findTagCollectionDoc = (tag: String) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag); + public static findTagCollectionDoc = (tag: string) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag); /** * Creates a Doc that collects Docs with the specified tag / value @@ -148,7 +149,7 @@ export class TagItem extends ObservableReactComponent { private _ref: React.RefObject; - constructor(props: any) { + constructor(props: TagItemProps) { super(props); makeObservable(this); this._ref = React.createRef(); @@ -225,7 +226,7 @@ export class TagItem extends ObservableReactComponent { type="checkbox" onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()} - onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')} + onChange={undoable(() => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')} checked={this.doc[metadata] as boolean} /> ) : ( @@ -257,7 +258,7 @@ interface TagViewProps { */ @observer export class TagsView extends ObservableReactComponent { - constructor(props: any) { + constructor(props: TagViewProps) { super(props); makeObservable(this); } @@ -271,7 +272,7 @@ export class TagsView extends ObservableReactComponent { componentDidMount() { this._heightDisposer = reaction( () => this.View.screenToContentsTransform(), - xf => { + () => { this._panelHeightDirty = this._panelHeightDirty + 1; } ); @@ -288,7 +289,7 @@ export class TagsView extends ObservableReactComponent { return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale()); } @computed get isEditing() { - return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document)); + return this._isEditing && (this._props.Views.length > 1 || (DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View))); } /** @@ -336,7 +337,7 @@ export class TagsView extends ObservableReactComponent { ); this._panelHeightDirty; - return this.View.ComponentView?.isUnstyledView?.() || (!this.View.Document._layout_showTags && this._props.Views.length === 1) ? null : ( + return this.View.ComponentView?.isUnstyledView?.() || (!this.View.showTags && this._props.Views.length === 1) ? null : (
r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} @@ -352,9 +353,10 @@ export class TagsView extends ObservableReactComponent { }}>
- {!tagsList.size && !facesList.size ? null : ( // + {this._props.Views.length === 1 && !this.View.showTags ? null : ( // this.setToEditing(!this._isEditing)} icon={} /> )} + {Array.from(tagsList).map((tag, i) => ( { />
- {TagItem.AllTagCollectionDocs.map((doc, i) => { + {TagItem.AllTagCollectionDocs.map(doc => { const tag = StrCast(doc.title); return ( - - ))} -
- ); - }; /** * Opens the filter panel in the properties menu @@ -93,13 +50,13 @@ export class IconTagBox extends ObservableReactComponent { }; /** - * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt - * @param childPairIndex * @param buttonID * @param doc */ - toggleButton = undoable((doc: Doc, icon: string) => { - BoolCast(doc[icon]) ? (doc[icon] = false) : (doc[icon] = true); + setIconTag = undoable((icon: string, state: boolean) => { + this._props.Views.forEach(view => { + view.dataDoc[icon] = state; + }); }, 'toggle card tag'); /** @@ -112,10 +69,43 @@ export class IconTagBox extends ObservableReactComponent { const isActive = doc[icon.toString()]; const color = isActive ? '#4476f7' : '#323232'; - return ; + return ; }; + /** + * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups + */ render() { - return <>{this.renderButtons(this._props.doc)}; + const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1; + + const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles); + + const iconMap = (buttonID: number) => { + return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp; + }; + const buttons = numberRange(amButtons - 1) + .filter(i => this._props.IsEditing || this.View.Document[iconMap(i).toString()] || (DocumentView.Selected.length === 1 && this.View.IsSelected)) + .map(i => ( + Click to add/remove this card from the {iconMap(i).toString()} group
}> + + + )); + return !buttons.length ? null : ( +
+ {buttons} +
+ ); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 343e255dc..e0331a422 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -275,6 +275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 1; - imgDocFaceDescriptions.forEach((fd, i) => { + imgDocFaceDescriptions.forEach(fd => { const faceDescriptor = new List(Array.from(fd.descriptor)); const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard); const faceAnno = Docs.Create.FreeformDocument([], { -- cgit v1.2.3-70-g09d2 From 302b9ab98001a50ca2117df823216a99b7c25869 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 19 Sep 2024 20:03:49 -0400 Subject: changed carousel to set #star tags instead of a metaadata field. --- src/client/views/FilterPanel.tsx | 41 +++++----------------- src/client/views/TagsView.tsx | 2 +- .../views/collections/CollectionCarouselView.tsx | 10 ++++-- 3 files changed, 17 insertions(+), 36 deletions(-) (limited to 'src/client/views/collections/CollectionCarouselView.tsx') diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index a3c8fc383..e34b66963 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -40,38 +40,17 @@ const HotKeyIconButton: React.FC = observer(({ hotKey /*, sel isEditing: false, myHotKey: hotKey, - toggleActive() { - this.isActive = !this.isActive; - }, - deactivate() { - this.isActive = false; - }, - startEditing() { - this.isEditing = true; - }, - stopEditing() { - this.isEditing = false; - }, - setHotKey(newHotKey: string) { - this.myHotKey.title = newHotKey; - }, - })); + toggleActive() { this.isActive = !this.isActive; }, + deactivate() { this.isActive = false; }, + startEditing() { this.isEditing = true; }, + stopEditing() { this.isEditing = false; }, + setHotKey(newHotKey: string) { this.myHotKey.title = newHotKey; }, + })); // prettier-ignore const panelRef = useRef(null); const inputRef = useRef(null); - const handleClick = () => { - state.toggleActive(); - }; - - const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); - - /** - * Removes a hotkey from list - */ - const removeHotKey = () => { - Doc.RemFromFilterHotKeys(hotKey); - }; + const handleClick = () => state.toggleActive(); /** * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new @@ -99,9 +78,7 @@ const HotKeyIconButton: React.FC = observer(({ hotKey /*, sel useEffect(() => { document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; + return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard'] as IconProp[]; @@ -172,7 +149,7 @@ const HotKeyIconButton: React.FC = observer(({ hotKey /*, sel className="hotKey-close" onClick={(e: React.MouseEvent) => { e.stopPropagation(); - removeHotKey(); + Doc.RemFromFilterHotKeys(hotKey); }}> diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 2f30edb80..f44fd1d03 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -85,7 +85,7 @@ export class TagItem extends ObservableReactComponent { public static allDocsWithTag = (tag: string) => DocListCast(TagItem.findTagCollectionDoc(tag)?.[DocData].docs); public static docHasTag = (doc: Doc, tag: string) => { - return StrListCast(doc.tags).includes(tag); + return StrListCast(doc?.tags).includes(tag); }; /** * Adds a tag to the metadata of this document and adds the Doc to the corresponding tag collection Doc (or creates it) diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 4609be374..974cd3e36 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -14,6 +14,7 @@ import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { TagItem } from '../TagsView'; enum cardMode { PRACTICE = 'practice', @@ -28,7 +29,7 @@ enum practiceVal { export class CollectionCarouselView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore - get starField() { return "star"; } // prettier-ignore + get starField() { return "#star"; } // prettier-ignore _fadeTimer: NodeJS.Timeout | undefined; _resetter: IReactionDisposer | undefined; @@ -108,7 +109,10 @@ export class CollectionCarouselView extends CollectionSubView() { star = (e: React.MouseEvent) => { e.stopPropagation(); const curDoc = this.carouselItems[this.carouselIndex]; - curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true); + if (curDoc) { + if (TagItem.docHasTag(curDoc, this.starField)) TagItem.removeTagFromDoc(curDoc, this.starField); + else TagItem.addTagToDoc(curDoc, this.starField); + } }; /* @@ -241,7 +245,7 @@ export class CollectionCarouselView extends CollectionSubView() {
- +
this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}> -- cgit v1.2.3-70-g09d2