From 0365f57b56cfee77930c25cfcc5bb5813423f4be Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Jan 2025 11:06:58 -0500 Subject: fixed drawing opaque strokes over images. --- src/client/documents/Documents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0bff74ac1..1ca118137 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -923,7 +923,7 @@ export namespace Docs { const I = Doc.GetProto(ink); // I.layout_hideOpenButton = true; // don't show open full screen button when selected I.color = color; - I.fillColor = fillColor; + I.fillColor = fillColor && fillColor !== 'transparent' ? fillColor : undefined; I.stroke = new InkField(points); I.stroke_width = strokeWidth; I.stroke_bezier = strokeBezier; -- cgit v1.2.3-70-g09d2 From 3bc00bae7d7569920d3d6f6221c7a57cabd80e35 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Jan 2025 11:41:40 -0500 Subject: adjusted dash field views to work with new schemacells (eg mermaids docs) --- src/client/views/collections/collectionSchema/SchemaRowBox.tsx | 8 ++------ src/client/views/nodes/formattedText/DashFieldView.tsx | 9 +++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 16f8b86f3..da203abfa 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -100,9 +100,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { return infos; } - isolatedSelection = (doc: Doc) => { - return this.schemaView?.selectionOverlap(doc); - }; + isolatedSelection = (doc: Doc) => this.schemaView?.selectionOverlap(doc); setCursorIndex = (mouseY: number) => this.schemaView?.setRelCursorIndex(mouseY); selectedCol = () => this.schemaView._selectedCol; getFinfo = computedFn((fieldKey: string) => this.schemaView?.fieldInfos.get(fieldKey)); @@ -113,9 +111,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { columnWidth = computedFn((index: number) => () => this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth); computeRowIndex = () => this.schemaView?.rowIndex(this.Document); highlightCells = (text: string) => this.schemaView?.highlightCells(text); - selectReference = (doc: Doc, col: number) => { - this.schemaView.selectReference(doc, col); - }; + selectReference = (doc: Doc, col: number) => this.schemaView.selectReference(doc, col); eqHighlightFunc = (text: string) => { const info = this.schemaView.findCellRefs(text); const cells: HTMLDivElement[] = []; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index f0313fba4..0684daeb6 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react'; import { NodeSelection } from 'prosemirror-state'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; +import { returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; @@ -169,12 +169,17 @@ export class DashFieldViewInternal extends ObservableReactComponent []} // fix + isolatedSelection={() => [true, true]} // fix + rowSelected={returnTrue} //fix rowHeight={returnZero} isRowActive={this.isRowActive} padding={0} getFinfo={emptyFunction} setColumnValues={returnFalse} - setSelectedColumnValues={returnFalse} allowCRs oneLine={!this._expanded && !this._props.nodeSelected()} finishEdit={this.finishEdit} -- cgit v1.2.3-70-g09d2 From 406706ab42bc1fdac81b3bffdcadfcfe05cfc8a0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Jan 2025 12:06:50 -0500 Subject: fixed placement of widget decorations --- src/client/views/StyleProvider.tsx | 9 +++------ src/client/views/nodes/DocumentView.tsx | 6 ++++-- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index e825a27d3..bebc9a341 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -1,7 +1,7 @@ +import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from '@dash/components'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from '@dash/components'; import { action, untracked } from 'mobx'; import { extname } from 'path'; import * as React from 'react'; @@ -10,6 +10,7 @@ import { FaFilter } from 'react-icons/fa'; import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; +import { InkInkTool } from '../../fields/InkField'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; import { AudioAnnoState } from '../../server/SharedMediaTypes'; @@ -21,11 +22,9 @@ import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; -import { styleProviderQuiz } from './StyleProviderQuiz'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; -import { TagsView } from './TagsView'; -import { InkInkTool } from '../../fields/InkField'; +import { styleProviderQuiz } from './StyleProviderQuiz'; function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground'); @@ -394,7 +393,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); }; - const tags = () => docView?.() ? : null; return ( <> @@ -402,7 +400,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f1b48be4a..5fd9223e3 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -52,6 +52,7 @@ import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails/PresEnums'; import SpringAnimation from './trails/SlideEffect'; import { SpringType, springMappings } from './trails/SpringUtils'; +import { TagsView } from '../TagsView'; export interface DocumentViewProps extends FieldViewSharedProps { hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected @@ -778,7 +779,7 @@ export class DocumentViewInternal extends DocComponent - {this.widgetDecorations ?? null} + {this._props.DocumentView?.() ? : null} ) : ( <> @@ -801,10 +802,11 @@ export class DocumentViewInternal extends DocComponent this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
{this._componentView?.componentAIView?.() ?? null} - {this.widgetDecorations ?? null} + {this._props.DocumentView?.() ? : null}
)} + {this.widgetDecorations ?? null} ); } -- 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') 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 a78901127cd8af401146df47595442c4d8a696f2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Jan 2025 11:01:05 -0500 Subject: from last --- src/client/documents/Documents.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1ca118137..6c2f10652 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -19,7 +19,6 @@ import { DocServer } from '../DocServer'; import { dropActionType } from '../util/DropActionTypes'; import { CollectionViewType, DocumentType } from './DocumentTypes'; import { Id } from '../../fields/FieldSymbols'; -import { FireflyImageData } from '../views/smartdraw/FireflyConstants'; class EmptyBox { public static LayoutString() { -- 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') 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') 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 (