diff options
author | bobzel <zzzman@gmail.com> | 2024-09-18 20:46:38 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2024-09-18 20:46:38 -0400 |
commit | d95730d904612640184ca6fdc00864b0c81b0c0c (patch) | |
tree | 29176e9a4ea75fca8a146e8a49774fd1b1fb3fc9 | |
parent | cf13604b06b8d8cf37f6e69f19a4092bf2c29d65 (diff) |
lots of changes to fix dragging cards, integrate iconTags with other tags, sizing docs when selected to fit window,
18 files changed, 198 insertions, 204 deletions
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<string>(['Star', 'Heart', 'Bolt', 'Cloud']); + Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['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 title={<div className="dash-tooltip">{tooltip}</div>}> - <div className="documentButtonBar-pinIcon"> - <FontAwesomeIcon - className="documentdecorations-icon" - style={{ width: 20 }} - key={icon.toString()} - size="sm" - icon={icon} - onClick={() => { - if (name === 'tags') { - targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags); - } else { - targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels); - } - }} - /> - </div> - </Tooltip> - ); - }; - return !targetDoc ? null : ( <div className="documentButtonBar-icon"> - <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}> + {/* <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}> {metaBtn('tags', 'star')} {metaBtn('keywords', 'id-card')} - </div> + </div> */} <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}> <div className="documentButtonBar-icon" style={{ color: 'white' }} - onClick={() => { - // 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')}> <FontAwesomeIcon className="documentdecorations-icon" icon="tag" /> </div> </Tooltip> 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<DocumentDecora <div className="link-button-container" style={{ - top: DocumentView.Selected().length > 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) `, }}> <DocumentButtonBar views={() => DocumentView.Selected()} /> @@ -846,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora <div className="documentDecorations-tagsView" style={{ - top: `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, + top: `${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().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : 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<Doc>, props: Opt<FieldViewProps & ); }; const tags = () => docView?.() ? <TagsView Views={[docView?.()]}/> : null; - const iconTags = () => doc?.[DocData].showIconTags ? <IconTagBox doc= {doc}/> : null; return ( <> @@ -375,7 +372,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & {filter()} {audio()} {tags()} - {iconTags()} </> ); } 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<TagItemProps> { * @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<TagItemProps> { private _ref: React.RefObject<HTMLDivElement>; - constructor(props: any) { + constructor(props: TagItemProps) { super(props); makeObservable(this); this._ref = React.createRef(); @@ -225,7 +226,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> { 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<TagViewProps> { - constructor(props: any) { + constructor(props: TagViewProps) { super(props); makeObservable(this); } @@ -271,7 +272,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> { componentDidMount() { this._heightDisposer = reaction( () => this.View.screenToContentsTransform(), - xf => { + () => { this._panelHeightDirty = this._panelHeightDirty + 1; } ); @@ -288,7 +289,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> { 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<TagViewProps> { ); 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 : ( <div className="tagsView-container" ref={r => 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<TagViewProps> { }}> <div className="tagsView-content" style={{ width: '100%' }}> <div className="tagsView-list"> - {!tagsList.size && !facesList.size ? null : ( // + {this._props.Views.length === 1 && !this.View.showTags ? null : ( // <IconButton style={{ width: '8px' }} tooltip="Close Menu" onPointerDown={() => this.setToEditing(!this._isEditing)} icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />} /> )} + <IconTagBox Views={this._props.Views} IsEditing={this._isEditing} /> {Array.from(tagsList).map((tag, i) => ( <TagItem key={i} @@ -388,7 +390,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> { /> </div> <div className="tagsView-suggestions-box"> - {TagItem.AllTagCollectionDocs.map((doc, i) => { + {TagItem.AllTagCollectionDocs.map(doc => { const tag = StrCast(doc.title); return ( <Button diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 5ccc3d9a8..e5fb7aba6 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -29,18 +29,13 @@ transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); } -.no-card-span{ +.no-card-span { position: relative; width: fit-content; text-align: center; font-size: 65px; - - - } - - .card-item-inactive, .card-item-active, .card-item { @@ -50,7 +45,6 @@ flex-direction: column; } - .card-item-inactive { opacity: 0.5; } diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index fced9fd37..1952cc707 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -21,6 +21,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { List } from '../../../fields/List'; enum cardSortings { Time = 'time', @@ -42,7 +43,6 @@ enum cardSortings { @observer export class CollectionCardView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; - private _childDocumentWidth = 600; // target width of a Doc... private _disposers: { [key: string]: IReactionDisposer } = {}; private _textToDoc = new Map<string, Doc>(); @@ -77,6 +77,12 @@ export class CollectionCardView extends CollectionSubView() { this.setRegenerateCallback(); } + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + this._dropDisposer?.(); + if (ele) { + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); + } + }; /** * Callback to ensure gpt's text versions of the child docs are updated */ @@ -94,10 +100,7 @@ export class CollectionCardView extends CollectionSubView() { }; componentDidMount() { - this.Document.childFilters_boolean = 'OR'; - this.childDocsWithoutLinks.forEach(c => { - c[DocData].showIconTags = true; - }); + this.Document.childFilters_boolean = 'OR'; // bcz: really shouldn't be assigning to fields from within didMount -- this should be a default/override beahavior somehow // Reaction to cardSort changes this._disposers.sort = reaction( @@ -117,28 +120,24 @@ export class CollectionCardView extends CollectionSubView() { this._dropDisposer?.(); } - @computed get cardSort_customField() { - return StrCast(this.Document.cardSort_customField) as 'chat' | 'star' | 'idea' | 'like'; - } - @computed get cardSort() { return StrCast(this.Document.cardSort) as cardSortings; } /** - * how much to scale down the contents of the view so that everything will fit + * The child documents to be rendered-- either all of them except the Links or the docs in the currently active + * custom group */ - @computed get fitContentScale() { - const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount); - return (this._childDocumentWidth * length) / this._props.PanelWidth(); + @computed get childDocsWithoutLinks() { + return (this.childDocList as Doc[]).filter(l => l.type !== DocumentType.LINK); } /** - * The child documents to be rendered-- either all of them except the Links or the docs in the currently active - * custom group + * how much to scale down the contents of the view so that everything will fit */ - @computed get childDocsWithoutLinks() { - return this.childDocs.filter(l => l.type !== DocumentType.LINK); + @computed get fitContentScale() { + const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount); + return (this.childPanelWidth() * length) / this._props.PanelWidth(); } /** @@ -154,7 +153,7 @@ export class CollectionCardView extends CollectionSubView() { * Number of rows of cards to be rendered */ @computed get numRows() { - return Math.ceil(this.sortedDocs.length / 10); + return Math.ceil(this.sortedDocs.length / this._maxRowCount); } @action @@ -177,8 +176,7 @@ export class CollectionCardView extends CollectionSubView() { */ inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d)); - panelWidth = () => this._childDocumentWidth; - panelHeight = (layout: Doc) => () => (this.panelWidth() * NumCast(layout._height)) / NumCast(layout._width); + childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2); onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); isChildContentActive = () => !!this.isContentActive(); @@ -245,6 +243,7 @@ export class CollectionCardView extends CollectionSubView() { const currRow = Math.floor((mouseY - 100) / rowHeight); //rows start at 0 if (adjustedX < 0) { + console.log('DROP INDEX NO '); return 0; // Before the first column } @@ -259,6 +258,7 @@ export class CollectionCardView extends CollectionSubView() { index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount; } + console.log('DROP INDEX = ' + index); return index; }; @@ -281,20 +281,41 @@ export class CollectionCardView extends CollectionSubView() { }; /** + * Handles external drop of images/PDFs etc from outside Dash. + */ + onExternalDrop = async (e: React.DragEvent): Promise<void> => { + super.onExternalDrop(e, {}); + }; + + /** * Resets all the doc dragging vairables once a card is dropped * @param e * @param de drop event * @returns true if a card has been dropped, falls if not */ - onInternalDrop = undoable((e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData) { - this._isACardBeingDragged = false; - this._docDraggedIndex = -1; - e.stopPropagation(); - return true; - } - return false; - }, ''); + onInternalDrop = undoable( + action((e: Event, de: DragManager.DropEvent) => { + if (de.complete.docDragData) { + this._isACardBeingDragged = false; + const dragIndex = this._docDraggedIndex; + if (dragIndex > -1) { + this._docDraggedIndex = -1; + const draggedDoc = DragManager.docsBeingDragged[0]; + const sorted = this.sortedDocs; + const originalIndex = sorted.findIndex(doc => doc === draggedDoc); + + this.Document.cardSort = ''; + sorted.splice(originalIndex, 1); + sorted.splice(dragIndex, 0, draggedDoc); + this.dataDoc[this.fieldKey] = new List<Doc>(sorted); + } + e.stopPropagation(); + return true; + } + return false; + }), + '' + ); @computed get sortedDocs() { return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex); @@ -348,28 +369,29 @@ export class CollectionCardView extends CollectionSubView() { * @returns */ sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => { - docs.sort((docA, docB) => { - const [typeA, typeB] = (() => { - switch (sortType) { - case cardSortings.Time: - return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; - case cardSortings.Color: { - const d1 = DashColor(StrCast(docA.backgroundColor)); - const d2 = DashColor(StrCast(docB.backgroundColor)); - return [d1.hsv().hue(), d2.hsv().hue()]; + sortType && + docs.sort((docA, docB) => { + const [typeA, typeB] = (() => { + switch (sortType) { + case cardSortings.Time: + return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; + case cardSortings.Color: { + const d1 = DashColor(StrCast(docA.backgroundColor)); + const d2 = DashColor(StrCast(docB.backgroundColor)); + return [d1.hsv().hue(), d2.hsv().hue()]; + } + case cardSortings.Tag: + return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999]; + case cardSortings.Chat: + return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999]; + default: + return [StrCast(docA.type), StrCast(docB.type)]; } - case cardSortings.Tag: - return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999]; - case cardSortings.Chat: - return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999]; - default: - return [StrCast(docA.type), StrCast(docB.type)]; - } - })(); + })(); - const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0; - return isDesc ? out : -out; - }); + const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0; + return isDesc ? out : -out; + }); if (dragIndex != -1) { const draggedDoc = DragManager.docsBeingDragged[0]; const originalIndex = docs.findIndex(doc => doc === draggedDoc); @@ -396,9 +418,11 @@ export class CollectionCardView extends CollectionSubView() { ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot isContentActive={emptyFunction} isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight(doc)} + PanelWidth={this.childPanelWidth} + PanelHeight={() => this._props.PanelHeight() * this.fitContentScale} + 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={true} dontHideOnDrag /> ); @@ -415,18 +439,18 @@ export class CollectionCardView extends CollectionSubView() { // 13 - 3 = 10 const totalCards = this.sortedDocs.length; // if 9 or less - if (index < totalCards - (totalCards % 10)) { + if (index < totalCards - (totalCards % this._maxRowCount)) { return this._maxRowCount; } // (3) - return totalCards % 10; + return totalCards % this._maxRowCount; }; /** * Determines the index a card is in in a row * @param realIndex * @returns */ - overflowIndexCalc = (realIndex: number) => realIndex % 10; + overflowIndexCalc = (realIndex: number) => realIndex % this._maxRowCount; /** * Translates the cards in the second rows and beyond over to the right * @param realIndex @@ -434,7 +458,7 @@ export class CollectionCardView extends CollectionSubView() { * @param calcRowCards * @returns */ - translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (10 - calcRowCards) * (this.panelWidth() / 2)); + translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (this._maxRowCount - calcRowCards) * (this.childPanelWidth() / 2)); /** * Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected @@ -613,6 +637,9 @@ export class CollectionCardView extends CollectionSubView() { const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2; return (rowCenterIndex - indexInRow) * 100 - 50; }; + const aspect = NumCast(doc.height) / NumCast(doc.width, 1); + const vscale = ((this._props.PanelHeight() * .95) * this.fitContentScale) / (aspect * this.childPanelWidth()); + const hscale = 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 ( <div key={doc[Id]} @@ -629,13 +656,13 @@ export class CollectionCardView extends CollectionSubView() { ); }} style={{ - width: this.panelWidth(), + width: this.childPanelWidth(), height: 'max-content', transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px) - translateX(calc(${(isSelected ? translateIfSelected() : 0) + '% + ' + this.translateOverflowX(realIndex, amCards) + 'px'})) + translateX(calc(${isSelected ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px)) rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg) - scale(${isSelected ? 2 : this._hoveredNodeIndex === index ? 1.05 : 1})`, - }} + scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`, + }} // prettier-ignore onMouseEnter={() => this.setHoveredNodeIndex(index)}> {this.displayDoc(doc, childScreenToLocal)} </div> @@ -645,14 +672,13 @@ export class CollectionCardView extends CollectionSubView() { render() { const isEmpty = this.childDocsWithoutLinks.length === 0; - const transformValue = `scale(${1 / this.fitContentScale})`; - const heightValue = `${100 * this.fitContentScale}%`; return ( <div onPointerMove={e => this.onPointerMove(e)} className="collectionCardView-outer" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} + onDrop={this.onExternalDrop.bind(this)} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string, color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string, @@ -660,8 +686,8 @@ export class CollectionCardView extends CollectionSubView() { <div className="card-wrapper" style={{ - ...(!isEmpty && { transform: transformValue }), - ...(!isEmpty && { height: heightValue }), + ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }), + ...(!isEmpty && { height: `${100 * this.fitContentScale}%` }), gridAutoRows: `${100 / this.numRows}%`, }} onMouseLeave={() => this.setHoveredNodeIndex(-1)}> diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index ad868fd1e..4609be374 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index e97ee713e..1ac0b6d70 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import * as CSS from 'csstype'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; @@ -540,7 +539,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection if (this.pivotField) { const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { - // eslint-disable-next-line prefer-destructuring type = types[0]; } } @@ -577,7 +575,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined; const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { - // eslint-disable-next-line prefer-destructuring type = types[0]; } const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))))); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 6aca8f2ca..99373da04 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -122,6 +122,9 @@ export function CollectionSubView<X>() { ); return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types } + /** + * This is the raw, stored list of children on a collection. If you modify this list, the database will be updated + */ @computed get childDocList() { return Cast(this.dataField, listSpec(Doc)); } @@ -218,7 +221,6 @@ export function CollectionSubView<X>() { if (!cursors) { proto.cursors = cursors = new List<CursorField>(); } - // eslint-disable-next-line no-cond-assign if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) { cursors[ind].setPosition(pos); } else { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index dbf781e63..cbbf063b4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; import { Property } from 'csstype'; @@ -1211,7 +1210,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection for (let j = 0; j < otherCtrlPts.length - 3; j += 4) { const neighboringSegment = i === j || i === j - 4 || i === j + 4; // Ensuring that the curve intersected by the eraser is not checked for further ink intersections. - // eslint-disable-next-line no-continue if (ink?.Document === otherInk.Document && neighboringSegment) continue; const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y }))); @@ -1481,8 +1479,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const childData = entry.pair.data; return ( <CollectionFreeFormDocumentView - // eslint-disable-next-line react/jsx-props-no-spreading, @typescript-eslint/no-explicit-any - {...(OmitKeys(entry, ['replica', 'pair']).omit as any)} + {...(OmitKeys(entry, ['replica', 'pair']).omit as { x: number; y: number; z: number; width: number; height: number })} key={childLayout[Id] + (entry.replica || '')} Document={childLayout} reactParent={this} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4c357cf45..758e70508 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -67,6 +67,7 @@ export interface DocumentViewProps extends FieldViewSharedProps { hideCaptions?: boolean; contentPointerEvents?: Property.PointerEvents | undefined; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents dontCenter?: 'x' | 'y' | 'xy'; + showTags?: boolean; childHideDecorationTitle?: boolean; childHideResizeHandles?: boolean; childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar. @@ -1126,6 +1127,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { @observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing @observable public TagPanelHeight = 0; + @computed get showTags() { + return this.Document._layout_showTags || this._props.showTags; + } + @computed private get shouldNotScale() { return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.(); } diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss index 211a961c1..90cc06092 100644 --- a/src/client/views/nodes/IconTagBox.scss +++ b/src/client/views/nodes/IconTagBox.scss @@ -2,23 +2,25 @@ .card-button-container { display: flex; - padding: 3px; - position: absolute; + position: relative; pointer-events: none; - background-color: rgb(218, 218, 218); + background-color: rgb(218, 218, 218); border-radius: 50px; - transform: translateY(25px); - align-items: center; - justify-content: start; + align-items: center; + gap: 5px; + padding-left: 5px; + padding-right: 5px; + padding-top: 2px; + padding-bottom: 2px; button { pointer-events: auto; - transform: translateY(-7.5px); - width: 30px; - height: 30px; + width: 20px; + height: 20px; + margin: auto; + padding: 0; border-radius: 50%; background-color: $dark-gray; - margin: 5px; background-color: transparent; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx index f86372ec0..3d021fd73 100644 --- a/src/client/views/nodes/IconTagBox.tsx +++ b/src/client/views/nodes/IconTagBox.tsx @@ -6,18 +6,18 @@ import { observer } from 'mobx-react'; import React from 'react'; import { numberRange } from '../../../Utils'; import { Doc, StrListCast } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; -import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { CollectionViewType } from '../../documents/DocumentTypes'; +import { StrCast } from '../../../fields/Types'; import { SnappingManager } from '../../util/SnappingManager'; import { undoable } from '../../util/UndoManager'; import { MainView } from '../MainView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { PropertiesView } from '../PropertiesView'; +import { DocumentView } from './DocumentView'; import './IconTagBox.scss'; export interface IconTagProps { - doc: Doc; + Views: DocumentView[]; + IsEditing: boolean; } /** @@ -26,59 +26,16 @@ export interface IconTagProps { */ @observer export class IconTagBox extends ObservableReactComponent<IconTagProps> { - @computed - get currentScale() { - return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1); - } - constructor(props: IconTagProps) { super(props); } - componentDidUpdate(): void { - this._props.doc[DocData].tagHeight = 36 * this.currentScale; + @computed get View() { + return this._props.Views.lastElement(); + } + @computed get currentScale() { + return this.View?.screenToLocalScale(); } - - /** - * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups - * @param doc - * @param cardSort - * @returns - */ - renderButtons = (doc: Doc): JSX.Element | null => { - const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1; - - const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles); - - const totalWidth = (amButtons - 1) * 35 + (amButtons - 1) * 2 * 5 + 6; - - const iconMap = (buttonID: number) => { - return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp; - }; - - const isCard = DocCast(this._props.doc.embedContainer).type_collection === CollectionViewType.Card; - - return ( - <div - className="card-button-container" - style={{ - transformOrigin: 'top left', - transform: `scale(${isCard ? 2 : 0.6 / this.currentScale}) - translateY(${doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) * (1 - this.currentScale) : 0}px) - `, - width: `${totalWidth}px`, - fontSize: '50px', - }}> - {numberRange(amButtons - 1).map(i => ( - <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}> - <button key={i} type="button" onClick={() => this.toggleButton(doc, iconMap(i))}> - {this.getButtonIcon(doc, iconMap(i))} - </button> - </Tooltip> - ))} - </div> - ); - }; /** * Opens the filter panel in the properties menu @@ -93,13 +50,13 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> { }; /** - * 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<IconTagProps> { const isActive = doc[icon.toString()]; const color = isActive ? '#4476f7' : '#323232'; - return <FontAwesomeIcon icon={icon} style={{ color, height: '30px', width: '30px' }} />; + return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />; }; + /** + * 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 => ( + <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}> + <button + key={i} + type="button" + onClick={() => { + const state = this.View.Document[iconMap(i).toString()]; + this.setIconTag(iconMap(i), !state); + }}> + {this.getButtonIcon(this.View.Document, iconMap(i))} + </button> + </Tooltip> + )); + return !buttons.length ? null : ( + <div + className="card-button-container" + style={{ + fontSize: '50px', + }}> + {buttons} + </div> + ); } } 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<FormattedTextB ele.append(contents); } this._selectionHTML = ele?.innerHTML; + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { /* empty */ } diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index c507e54b6..6f70e96ab 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -30,6 +30,7 @@ import { DocumentManager } from '../../util/DocumentManager'; * face_annos - a list of face annotations, where each anno has */ export class FaceRecognitionHandler { + // eslint-disable-next-line no-use-before-define static _instance: FaceRecognitionHandler; private _apiModelReady = false; private _pendingAPIModelReadyDocs: Doc[] = []; @@ -221,7 +222,7 @@ export class FaceRecognitionHandler { const annos = [] as Doc[]; const scale = NumCast(imgDoc.data_nativeWidth) / img.width; const showTags= imgDocFaceDescriptions.length > 1; - imgDocFaceDescriptions.forEach((fd, i) => { + imgDocFaceDescriptions.forEach(fd => { const faceDescriptor = new List<number>(Array.from(fd.descriptor)); const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard); const faceAnno = Docs.Create.FreeformDocument([], { |