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 +++--- 1 file changed, 3 insertions(+), 3 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; }; -- 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 (