From abacdb311ee658b74f009106f4e7751fc216af4b Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Thu, 18 Jul 2024 11:24:12 -0400 Subject: boutta get silly --- src/client/views/DocumentButtonBar.tsx | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a75c7098c..3d6436cb5 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -286,6 +286,42 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( @computed get keywordButton() { const targetDoc = this.view0?.Document; + + const metaBtn = (name: string, icon: IconProp) => { + const tooltip = `Show ${name}`; + return ( + {tooltip}}> +
+ { + // this.subPin = + // (pinLayoutView ? 'Layout' : '') + + // (pinLayoutView && pinContentView ? ' &' : '') + + // (pinContentView ? ' Content View' : '') + + // (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : ''); + // })} + // onPointerLeave={action(() => { + // this.subPin = ''; + // })} + onClick={e => { + name === 'keywords' ? targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels) : 'hi' + + + }} + /> +
+
+ ); + }; + + + + return !targetDoc ? null : ( Open keyword menu}>
( onClick={() => { targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels; }}> + +
+ {/* {pinBtn(true, false, 'window-maximize')} + {pinBtn(false, true, 'address-card')} + {pinBtn(true, true, 'id-card')} */} +
-- cgit v1.2.3-70-g09d2 From de253f5acca34f20017895a2d8469b5ebd6032bf Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Tue, 30 Jul 2024 14:31:00 -0400 Subject: fight w css --- src/client/apis/gpt/GPT.ts | 9 ++ src/client/documents/Documents.ts | 2 + src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/DocumentButtonBar.tsx | 6 +- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/FilterPanel.tsx | 95 ++++++++++++ src/client/views/StyleProvider.tsx | 7 + .../views/collections/CollectionCardDeckView.scss | 50 +++--- .../views/collections/CollectionCardDeckView.tsx | 1 + src/client/views/nodes/IconTagBox.scss | 30 ++++ src/client/views/nodes/IconTagBox.tsx | 168 +++++++++++++++++++++ src/client/views/pdf/GPTPopup/GPTPopup.scss | 3 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 164 ++++++++++++++------ 13 files changed, 460 insertions(+), 81 deletions(-) create mode 100644 src/client/views/nodes/IconTagBox.scss create mode 100644 src/client/views/nodes/IconTagBox.tsx (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 40af70d99..d95e564c7 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -12,6 +12,7 @@ enum GPTCallType { DESCRIBE = 'describe', MERMAID = 'mermaid', DATA = 'data', + RUBRIC = 'rubric' } type GPTCallOpts = { @@ -26,6 +27,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { summary: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' }, edit: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' }, flashcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Make flashcards out of this text with each question and answer labeled. Do not label each flashcard and do not include asterisks: ' }, + completion: { model: 'gpt-4-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." }, mermaid: { model: 'gpt-4-turbo', @@ -54,6 +56,13 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { temp: 0, prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct', }, + + rubric: { + model: 'gpt-4-turbo', + maxTokens: 1024, + temp: 0, + prompt: "Provide a definition for the vollowing term. It will be used as a rubric to evaluate the user's understanding of the topic", + }, }; let lastCall = ''; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e8e9b4fb6..612629f89 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -493,6 +493,8 @@ export class DocumentOptions { // cardSort_visibleSortGroups?: List; // which sorting values are being filtered (shown) cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending'); // test?: STRt = new StrInfo('testing for filtering') + keywords?: MAPt = new MapInfo('keywords', true); + } export const DocOptions = new DocumentOptions(); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 380d16686..67f8dafc4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -39,7 +39,7 @@ import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; -interface Button { +export interface Button { // DocumentOptions fields a button can set title?: string; toolTip?: string; @@ -714,7 +714,7 @@ pie title Minerals in my tap water // } static tagGroupTools(): Button[] { return [ - { title: "Star", icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "Star", icon: StrCast(Doc.UserDoc().myFilterHotKeyIcons) ?? "Star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 3d6436cb5..c8df66535 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -309,7 +309,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( // this.subPin = ''; // })} onClick={e => { - name === 'keywords' ? targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels) : 'hi' + name === 'tags' ? targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags) : 'hi' }} @@ -332,7 +332,9 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( }}>
- {/* {pinBtn(true, false, 'window-maximize')} + { + metaBtn('tags', "tag") + /* {pinBtn(true, false, 'window-maximize')} {pinBtn(false, true, 'address-card')} {pinBtn(true, true, 'id-card')} */}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index dc40562e8..b5d819b97 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -832,7 +832,7 @@ export class DocumentDecorations extends ObservableReactComponent DocumentView.Selected()} /> diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 5db627d23..332a89a96 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -19,6 +19,16 @@ import './FilterPanel.scss'; import { DocumentView } from './nodes/DocumentView'; import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components'; import { ObservableReactComponent } from './ObservableReactComponent'; +import { Button } from '../util/CurrentUserUtils'; +import { ButtonType } from './nodes/FontIconBox/FontIconBox'; +import { DocCast } from '../../fields/Types'; +// import { Docs } from '../../documents/Documents'; +import { Docs } from '../documents/Documents'; +import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import { DocumentOptions } from '../documents/Documents'; +import { DocUtils } from '../documents/DocUtils'; +import { dropActionType } from '../util/DropActionTypes'; + interface filterProps { Document: Doc; @@ -217,6 +227,83 @@ export class FilterPanel extends ObservableReactComponent { return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; + addHotkey() { + + const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); + const filter = buttons['Filter'] + const filter2 = DocCast(filter) + const but2 = Doc.UserDoc().myContextMenuBtns + + // const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), { + // _width: 250, + // _height: 200, + // _layout_fitWidth: false, + // _layout_autoHeight: true, + // }); + + // CurrentUserUtils.setupContextMenuButtons() + + // const hm = Docs.Create.FontIconDocument() + + // DocumentView.getDocumentView(filter2)?.ComponentView?.addDocument?.(hm) + + // console.log(DocumentView.getDocumentView(filter2) + "hiiiii") + + // DocumentView.getDocumentView(DocCast(buttons['Card']))?.ComponentView?.addDocument?.(hm) + + const hi = CurrentUserUtils.contextMenuTools() + + const newKey: Button = { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}} + + + const heyy = [...hi, newKey] + + Doc.UserDoc().myFilterHotKeyIcons = "palette" + Doc.UserDoc().myFilterHotKeyTitles = "hi" + + CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc()); + + + + Doc.UserDoc().hi = Docs.Create.FontIconDocument( + { title: '', + icon: 'map-pin', + + backgroundColor: '#ACCEF7', + layout_hideAllLinks: true, + _width: 15, + _height: 15, + _xPadding: 0, + } + ) + + + // buttons['Filter'] + + + + + const reqdCtxtOpts:DocumentOptions = { title: "hi", undoIgnoreFields:new List(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; + const ctxtMenuBtns = CurrentUserUtils.setupContextMenuBtn(newKey, buttons); + // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns); + + + DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]); + + + // newCol && dv.ComponentView?.addDocument?.(newCol); + + // console.log(but2 + "omgg") + + + + // doc.cardSort_activeIcons = new List(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]); + + + + // Doc.UserDoc().myContextMenuBtns = new List([...buttons, newKey as Doc]) + } + render() { return (
@@ -283,6 +370,14 @@ export class FilterPanel extends ObservableReactComponent { ) )}
+
+
+
+ +
+
+ +
); } diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 40d0358d2..55e6217ff 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -30,6 +30,7 @@ import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; +import { IconTagBox } from './nodes/IconTagBox'; function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground'); @@ -377,6 +378,11 @@ export function DefaultStyleProvider(doc: Opt, props: Opt) } } + + const iconTags = () => { + if (doc && doc![DocData].showIconTags) + {return ()} + } return ( <> {paint()} @@ -384,6 +390,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); } diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index eb1b456f5..5869f89e1 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -22,32 +22,32 @@ transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); } -.card-button-container { - display: flex; - padding: 3px; - // width: 300px; - // height:100px; - pointer-events: none; /* This ensures the container does not capture hover events */ - - background-color: rgb(218, 218, 218); /* Background color of the container */ - border-radius: 50px; /* Rounds the corners of the container */ - transform: translateY(25px); - // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ - align-items: center; /* Centers buttons vertically */ - justify-content: start; /* Centers buttons horizontally */ - - button { - pointer-events: auto; /* Re-enable pointer events for the buttons */ +// .card-button-container { +// display: flex; +// padding: 3px; +// // width: 300px; +// // height:100px; +// pointer-events: none; /* This ensures the container does not capture hover events */ + +// background-color: rgb(218, 218, 218); /* Background color of the container */ +// border-radius: 50px; /* Rounds the corners of the container */ +// transform: translateY(25px); +// // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ +// align-items: center; /* Centers buttons vertically */ +// justify-content: start; /* Centers buttons horizontally */ + +// button { +// pointer-events: auto; /* Re-enable pointer events for the buttons */ - width: 70px; - height: 70px; - border-radius: 50%; - background-color: $dark-gray; - // border-color: $medium-blue; - margin: 5px; // transform: translateY(-50px); - background-color: transparent; - } -} +// width: 70px; +// height: 70px; +// border-radius: 50%; +// background-color: $dark-gray; +// // border-color: $medium-blue; +// margin: 5px; // transform: translateY(-50px); +// background-color: transparent; +// } +// } .no-card-span{ position: relative; diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 410ddc355..736cc2354 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -446,6 +446,7 @@ export class CollectionCardView extends CollectionSubView() { }; const docTextPromises = this.childDocsWithoutLinks.map(async doc => { const docText = (await docToText(doc)) ?? ''; + doc['gptInputText'] = docText this._textToDoc.set(docText.replace(/\n/g, ' ').trim(), doc); return `======${docText.replace(/\n/g, ' ').trim()}======`; }); diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss new file mode 100644 index 000000000..8c0f92c90 --- /dev/null +++ b/src/client/views/nodes/IconTagBox.scss @@ -0,0 +1,30 @@ +@import '../global/globalCssVariables.module.scss'; + +.card-button-container { + display: flex; + padding: 3px; + position: absolute; + // width: 300px; + // height:100px; + pointer-events: none; /* This ensures the container does not capture hover events */ + + background-color: rgb(218, 218, 218); /* Background color of the container */ + border-radius: 50px; /* Rounds the corners of the container */ + transform: translateY(25px); + // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ + align-items: center; /* Centers buttons vertically */ + justify-content: start; /* Centers buttons horizontally */ + + button { + pointer-events: auto; /* Re-enable pointer events for the buttons */ + transform: translateY(-7.5px); + + width: 30px; + height: 30px; + border-radius: 50%; + background-color: $dark-gray; + // border-color: $medium-blue; + margin: 5px; // transform: translateY(-50px); + background-color: transparent; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx new file mode 100644 index 000000000..9a2273c3a --- /dev/null +++ b/src/client/views/nodes/IconTagBox.tsx @@ -0,0 +1,168 @@ +import React from "react"; +import { observer } from "mobx-react"; +import { computed } from "mobx"; + +import { ObservableReactComponent } from "../ObservableReactComponent"; +import { NumCast } from "../../../fields/Types"; +import { makeObservable } from "mobx"; +import { Doc } from "../../../fields/Doc"; +import { Reaction } from "mobx"; +import { reaction } from "mobx"; +import { numberRange } from "../../../Utils"; +import { Tooltip } from "@mui/material"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { undoable } from "../../util/UndoManager"; +import { BoolCast } from "../../../fields/Types"; +import { DocCast } from "../../../fields/Types"; +import './IconTagBox.scss'; +import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../fields/DocSymbols'; + + +export interface IconTagProps { + doc: Doc; + + +} + +@observer +export class IconTagBox extends ObservableReactComponent { + private ref: React.RefObject; + private height: number = 0; + + + @computed + get currentScale() { + return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1); + } + + constructor(props: any) { + super(props); + makeObservable(this); + this.ref = React.createRef(); + } + + componentDidMount(): void { + this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + this._props.doc._keywordHeight = this.height; + + reaction( + () => this.currentScale, + () => { + if (this.currentScale < 1) { + this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + this._props.doc._keywordHeight = this.height; + } + } + ); + } + + componentDidUpdate(prevProps: Readonly): void { + this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + this._props.doc._keywordHeight = this.height; + } + + /** + * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups + * @param doc + * @param cardSort + * @returns + */ + renderButtons = (doc: Doc): JSX.Element | null => { + // if (cardSort !== cardSortings.Custom) return null; + + const amButtons = 4 + + // const amButtons = Math.max( + // 4, + // this.childDocs?.reduce((set, d) => { + // if (this.cardSort_customField) { + // set.add(NumCast(d[this.cardSort_customField])); + // } + // return set; + // }, new Set()).size ?? 0 + // ); + + // const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc); + + const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6; + + const iconMap: { [key: number]: any } = { + 0: 'star', + 1: 'heart', + 2: 'cloud', + 3: 'bolt' + }; + + return ( +
+ {numberRange(amButtons).map(i => ( + Click to add/remove this card from the {iconMap[i]} group
}> + + + ))} + + ); + }; + + /** + * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt + * @param childPairIndex + * @param buttonID + * @param doc + */ + toggleButton = undoable((doc: Doc, icon: string) => { + + + + // this.cardSort_customField && (doc[this.cardSort_customField] = buttonID); + + // doc.cardSort_activeIcons = new List() + + + // const list = StrListCast(doc.cardSort_activeIcons); + // doc.cardSort_activeIcons = new List(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]); + + BoolCast(doc[icon]) ? doc[icon] = false : doc[icon] = true + + + + // StrListCast(doc.cardSort_activeIcons).push(iconMap[buttonID]) + }, 'toggle card tag'); + + + getButtonIcon = (doc: Doc, icon: any): JSX.Element => { + + // const isActive = StrListCast(doc.cardSort_activeIcons).includes(icon) + const isActive = doc[icon] + + // console.log(StrListCast(doc.cardSort_activeIcons)) + const color = isActive ? '#4476f7' : '#323232'; + + return ; + }; + + render (){ + return ( + <> + {this.renderButtons(this._props.doc)} + + + ) + + } + + + +} + + diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 57973ef34..eaa3eaebf 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -66,8 +66,9 @@ $highlightedText: #82e0ff; .content-wrapper { padding-top: 10px; min-height: 50px; - max-height: 150px; + // max-height: 150px; overflow-y: auto; + height: 100% } .btns-wrapper-gpt { diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index bbd5ea630..a41c33a4d 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -19,7 +19,9 @@ import { AnchorMenu } from '../AnchorMenu'; import './GPTPopup.scss'; import { SettingsManager } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; - +import { DocumentView } from '../../nodes/DocumentView'; +import { DocCast } from '../../../../fields/Types'; +import { RTFCast } from '../../../../fields/Types'; export enum GPTPopupMode { SUMMARY, @@ -177,6 +179,59 @@ export class GPTPopup extends ObservableReactComponent { this.quizAnswer = e.target.value; }); + generateQuiz = async () => { + this.setLoading(true); + this.setSortDone(false); + + + 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 rubricText = 'Rubric: ' + StrCast(RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text); + const queryText = questionText + ' UserAnswer: ' + this.quizAnswer + '. ' + 'Rubric' + rubricText; + + try { + const res = await gptAPICall(queryText, GPTCallType.QUIZ); + if (!res) { + console.error('GPT call failed'); + return; + } + console.log(res) + this.setQuizResp(res) + + this.setLoading(false); + this.setSortDone(true); + + // this._outputValue = res; + } catch (err) { + console.error('GPT call failed'); + } + + + } + + generateRubric = async (inputText: string, doc:Doc) => { + try { + const res = await gptAPICall(inputText, GPTCallType.RUBRIC); + console.log(res + "rubbbb") + // if (!res) { + // console.error('GPT call failed'); + // return; + // } + doc['gptRubric']= res; + return res + } catch (err) { + console.error('GPT call failed'); + } + + } + @observable private regenerateCallback: (() => Promise) | null = null; @@ -192,6 +247,14 @@ export class GPTPopup extends ObservableReactComponent { public createFilteredDoc: (axes?: any) => boolean = () => false; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; + @observable quizRespText: string = '' + + @action setQuizResp (resp: string) { + this.quizRespText = resp + + } + + /** * Sorts cards in the CollectionCardDeckView */ @@ -403,7 +466,8 @@ export class GPTPopup extends ObservableReactComponent { ) cardActual = (opt: GPTPopupMode) => { - if (opt === GPTPopupMode.SORT) { + const isSort = opt === GPTPopupMode.SORT + // if (opt === GPTPopupMode.SORT) { return ( !this.sortDone ? ( <> @@ -412,24 +476,24 @@ export class GPTPopup extends ObservableReactComponent { className="searchBox-input" defaultValue="" autoComplete="off" - onChange={this.sortPromptChanged} + onChange={isSort ? this.sortPromptChanged : this.quizAnswerChanged} onKeyDown={e => { if (e.key === 'Enter') { - this.generateSort(); + isSort ? this.generateSort() : this.generateQuiz(); } e.stopPropagation(); }} type="text" - placeholder="How do you want to sort your cards?" + placeholder={`${isSort ? 'How do you want to sort your cards?' : 'What is the selected card?'}`} id="search-input" style={{ width: '100%' }} /> {/*
*/}
- - ); - } + // } else if (opt === GPTPopupMode.QUIZ) { + // return ( + // <> + //
+ // { + // if (e.key === 'Enter') { + // this.generateQuiz(); + // } + // e.stopPropagation(); + // }} + // type="text" + // placeholder="What is the selected card?" + // id="search-input" + // style={{ width: '100%' }} + // /> + // {/*
+ //
*/} + //
+ // + // ); + // } }; sortBox = () => (
- {this.heading('SORTING')} + {this.heading(this.mode === GPTPopupMode.SORT ? 'SORTING' : 'QUIZ')} <> {!this.cardsDoneLoading || this.loading ? (
@@ -673,7 +737,7 @@ export class GPTPopup extends ObservableReactComponent { tooltip="back" icon={} onClick={() => this.mode = GPTPopupMode.CARD} - style = {{right: '-55px'}} + style = {{right: '-20%'}} /> )} Date: Tue, 6 Aug 2024 14:48:49 -0400 Subject: locking into hotkey buttons --- src/client/util/CurrentUserUtils.ts | 24 +-- src/client/views/DocumentButtonBar.scss | 5 + src/client/views/DocumentButtonBar.tsx | 43 ++--- src/client/views/DocumentDecorations.tsx | 9 +- src/client/views/FilterPanel.scss | 64 +++++++ src/client/views/FilterPanel.tsx | 285 ++++++++++++++++++++++++++----- src/client/views/MainView.tsx | 3 +- src/client/views/nodes/IconTagBox.tsx | 38 +++-- 8 files changed, 380 insertions(+), 91 deletions(-) (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 67f8dafc4..0d8955ced 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -44,6 +44,7 @@ export interface Button { title?: string; toolTip?: string; icon?: string; + isSystem?: boolean; btnType?: ButtonType; numBtnMin?: number; numBtnMax?: number; @@ -713,14 +714,14 @@ pie title Minerals in my tap water // ] // } static tagGroupTools(): Button[] { - return [ - { title: "Star", icon: StrCast(Doc.UserDoc().myFilterHotKeyIcons) ?? "Star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Heart", icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Bolt", icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Options", icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton, expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - - + // hack: if there's no dashboard, create default filters. otherwise, just make sure that the Options button is preserved + return (Doc.UserDoc().activeDashboard ? [] : [ + { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + + // { title: "Group 1", icon, toolTip:"Click to toggle group 1's visibility", btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // { title: "Group 2", icon, toolTip:"Click to toggle group 2's visibility", btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // { title: "Group 3", icon, toolTip:"Click to toggle group 3's visibility", btnType: ButtonType.ToggleButton, width: 40, expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, @@ -728,7 +729,9 @@ pie title Minerals in my tap water // { title: "", icon, toolTip:"Click to toggle group 5's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // { title: "", icon, toolTip:"Click to toggle group 6's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // { title: "", icon, toolTip:"Click to toggle group 7's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, - ] + ]).concat([ + { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton, expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}} + ]) } static viewTools(): Button[] { return [ @@ -871,8 +874,9 @@ pie title Minerals in my tap water /// initializes a context menu button for the top bar context menu static setupContextMenuButton(params:Button, btnDoc?:Doc, btnContainer?:Doc) { const reqdOpts:DocumentOptions = { + isSystem: true, ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, - color: Colors.WHITE, isSystem: true, + color: Colors.WHITE, _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth, toolType: params.toolType, expertMode: params.expertMode, diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index 374d6ecd3..ede277aae 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -29,6 +29,11 @@ $linkGap: 3px; background: black; height: 20px; align-items: center; + + .tags { + width: 40px; + + } } .documentButtonBar-followTypes { width: 20px; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c8df66535..c61ebfb4c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -288,7 +288,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( const targetDoc = this.view0?.Document; const metaBtn = (name: string, icon: IconProp) => { - const tooltip = `Show ${name}`; + const tooltip = `Toggle ${name}`; return ( {tooltip}
}>
@@ -298,18 +298,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( key={icon.toString()} size="sm" icon={icon} - // onPointerEnter={action(() => { - // this.subPin = - // (pinLayoutView ? 'Layout' : '') + - // (pinLayoutView && pinContentView ? ' &' : '') + - // (pinContentView ? ' Content View' : '') + - // (pinLayoutView && pinContentView ? '(shift+alt)' : pinLayoutView ? '(shift)' : pinContentView ? '(alt)' : ''); - // })} - // onPointerLeave={action(() => { - // this.subPin = ''; - // })} onClick={e => { - name === 'tags' ? targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags) : 'hi' + // console.log('wtfff') + // name === 'tags' ?? + if (name === 'tags'){ + (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags)) + } else { + (targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels)) + } + + }} @@ -323,24 +321,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( return !targetDoc ? null : ( +
+
+ {metaBtn('tags', 'star')} + {metaBtn("keywords", 'id-card')} +
+ Open keyword menu
}>
{ - targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels; - }}> + // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags; + }} + > -
- { - metaBtn('tags', "tag") - /* {pinBtn(true, false, 'window-maximize')} - {pinBtn(false, true, 'address-card')} - {pinBtn(true, true, 'id-card')} */} -
+
+
+ ); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b5d819b97..f3b4013f7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -703,6 +703,7 @@ export class DocumentDecorations extends ObservableReactComponent CollectionFreeFormDocumentView.from(v)); + + + const keyWordTrans = doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) : 0 + const tagTrans = doc[DocData].showIconTags ? NumCast(doc[DocData].tagHeight) : 0 + + return (
DocumentView.Selected()} /> diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss index d6d2956aa..1877cc78b 100644 --- a/src/client/views/FilterPanel.scss +++ b/src/client/views/FilterPanel.scss @@ -1,3 +1,4 @@ + .filterBox-flyout { display: block; text-align: left; @@ -228,6 +229,69 @@ vertical-align: middle; } +.filterHotKey-button { + pointer-events: auto; + // padding-right: 8px; //5px; + width: 100%; //width: 25px; + border-radius: 5px; + // margin-right: 20px; + margin-bottom: 8px; + border-color: #d3d3d3; + border-style: solid; + border-width: thin; + transition: all 0.3s ease-out; + + &:hover{ + border-color: #e9e9e9; + background-color: #878484 + } + + + + &.active { + background-color: #878484; + + + + .icon-panel{ + display: flex + } + } +} + +.hotKeyButtons { + position: relative; + width: 100%; + // margin-top: 3px; + // // grid-column: 1/4; + // width: 100%; + // height: auto; + // display: flex; + // // flex-direction: row; + // // flex-wrap: wrap; + // padding-bottom: 5.5px; +} + +.hotKey-icon-button { + // pointer-events: auto; /* Re-enable pointer events for the buttons */ + + // width: 30px; + // height: 30px; + // border-color: $medium-blue; + background-color: transparent; + &.active{ + + + } + +} + +.icon-panel { + bottom: -14px; + + + +} // .sliderBox-outerDiv { // width: 30%;// width: calc(100% - 14px); // 14px accounts for handles that are at the max value of the slider that would extend outside the box // height: 40; // height: 100%; diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 332a89a96..e5f9fdb51 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -28,6 +28,19 @@ import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { DocumentOptions } from '../documents/Documents'; import { DocUtils } from '../documents/DocUtils'; import { dropActionType } from '../util/DropActionTypes'; +import { Toggle } from 'browndash-components'; +import { SettingsManager } from '../util/SettingsManager'; +import { StrCast } from '../../fields/Types'; +import { ToggleType } from 'browndash-components'; +import { MultiToggle } from 'browndash-components'; +import { Type } from 'browndash-components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { DocData } from '../../fields/DocSymbols'; +import { DocumentType } from '../documents/DocumentTypes'; +import { Tooltip } from '@mui/material'; +import { useLocalObservable } from 'mobx-react'; +import { useRef } from 'react'; +import { useEffect } from 'react'; interface filterProps { @@ -228,11 +241,14 @@ export class FilterPanel extends ObservableReactComponent { }; addHotkey() { - const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); - const filter = buttons['Filter'] - const filter2 = DocCast(filter) - const but2 = Doc.UserDoc().myContextMenuBtns + const filter = DocCast(buttons.Filter); + const filter2 = DocCast(filter); + const but2 = Doc.UserDoc().myContextMenuBtns; + + console.log(DocCast(filter).data); + + //[idToDoc("0bec0963-95ac-4978-8606-8af4c7b9badd"),idToDoc("f740e910-bf83-4ee3-b13a-1e63c49dd254"),idToDoc("61db82b0-025a-4cb9-937b-97b1290e8f1a"),idToDoc("1046859f-34ac-4421-bc07-ee2f2cf812e2"),idToDoc("badaff47-3c3a-4b78-a7d6-f2cbc8e45ba0")] // const newCol = Docs.Create.CarouselDocument(DocListCast(doc[Doc.LayoutFieldKey(doc)]), { // _width: 250, @@ -251,59 +267,135 @@ export class FilterPanel extends ObservableReactComponent { // DocumentView.getDocumentView(DocCast(buttons['Card']))?.ComponentView?.addDocument?.(hm) - const hi = CurrentUserUtils.contextMenuTools() - - const newKey: Button = { title: "Cloud", icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}} - - - const heyy = [...hi, newKey] - - Doc.UserDoc().myFilterHotKeyIcons = "palette" - Doc.UserDoc().myFilterHotKeyTitles = "hi" - - CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc()); - - - - Doc.UserDoc().hi = Docs.Create.FontIconDocument( - { title: '', - icon: 'map-pin', - - backgroundColor: '#ACCEF7', - layout_hideAllLinks: true, - _width: 15, - _height: 15, - _xPadding: 0, - } - ) - - - // buttons['Filter'] + // const hi = CurrentUserUtils.contextMenuTools() + + const newKey: Button = { + title: 'Bob', + icon: 'cloud', + toolTip: "Click to toggle the cloud group's visibility", + btnType: ButtonType.ToggleButton, + expertMode: false, + toolType: 'cloud', + funcs: {}, + scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}' }, + }; + + // const heyy = [...hi, newKey] + + Doc.UserDoc().myFilterHotKeyTitles = new List(['hi hi', 'supppp']) + + Doc.UserDoc()['hi hi'] = 'heart' + Doc.UserDoc()['supppp'] = 'star' + + // CurrentUserUtils.setupContextMenuButtons(Doc.UserDoc()); + + // Doc.UserDoc().hi = Docs.Create.FontIconDocument( + // { title: '', + // icon: 'map-pin', + + // backgroundColor: '#ACCEF7', + // layout_hideAllLinks: true, + // _width: 15, + // _height: 15, + // _xPadding: 0, + // } + // ) + + // // buttons['Filter'] + + // const reqdCtxtOpts: DocumentOptions = { + // title: 'Filter', + // undoIgnoreFields: new List(['width', 'linearView_IsOpen']), + // flexGap: 0, + // childDragAction: dropActionType.embed, + // childDontRegisterViews: true, + // linearView_IsOpen: true, + // ignoreClick: true, + // linearView_Expandable: false, + // _height: 35, + // }; + const ctxtMenuBtn = CurrentUserUtils.setupContextMenuBtn(newKey, filter); + ctxtMenuBtn.isSystem = ctxtMenuBtn[DocData].isSystem = undefined; + Doc.AddDocToList(filter, 'data', ctxtMenuBtn); + // // // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns); + + // DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]); + // newCol && dv.ComponentView?.addDocument?.(newCol); + // console.log(but2 + "omgg") + // doc.cardSort_activeIcons = new List(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]); - const reqdCtxtOpts:DocumentOptions = { title: "hi", undoIgnoreFields:new List(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; - const ctxtMenuBtns = CurrentUserUtils.setupContextMenuBtn(newKey, buttons); - // DocUtils.AssignOpts(buttons, reqdCtxtOpts, ctxtMenuBtns); + // Doc.UserDoc().myContextMenuBtns = new List([...buttons, newKey as Doc]) + } + hotKeyButtons() { + const selected = DocumentView.SelectedDocs().lastElement(); - DocUtils.AssignOpts(DocCast(buttons['Filter']), reqdCtxtOpts, [ctxtMenuBtns]); + function isAttrFiltered(attr: string) { + if (selected && selected._childFilters != undefined && selected.type === DocumentType.COL) { + return StrListCast(selected._childFilters).some(filter => filter.includes(attr)); + } else { + return false; + } + // return selected._childFilters ? StrListCast(selected._childFilters).some(filter => filter.includes(attr)) : false; + } + const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles); - // newCol && dv.ComponentView?.addDocument?.(newCol); + console.log(hotKeys + 'hiihihi'); - // console.log(but2 + "omgg") + const hi = ['star', 'heart', 'bolt']; + - // doc.cardSort_activeIcons = new List(list.includes(icon) ? list.filter(d => d !== icon) : [...list, icon]); + //selecting a button should make t so that the the icon on the top filter panel becomes said icon + const buttons = hotKeys.map((hotKey, i) => ( + //
{ + // // if (e.target === e.currentTarget){ + // isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match'); + // // } + // }, hotKey)} - // Doc.UserDoc().myContextMenuBtns = new List([...buttons, newKey as Doc]) + // // style = {{backgroundColor: `${isAttrFiltered(hotKey) ? '#878484' : SettingsManager.userBackgroundColor}`}} + + + // > + {/* } + iconPlacement="left" + align="flex-start" + fillWidth + toggleType={ToggleType.BUTTON} + onClick={undoable(() => { + isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match'); + }, hotKey)} + /> */} + <> + + Click to customize this hotkey's icon
}> + + + + + + )); + + return buttons; } + // @observable iconPanelMap: Map = new Map(); + render() { return (
@@ -376,8 +468,9 @@ export class FilterPanel extends ObservableReactComponent {
-
+ +
{this.hotKeyButtons()}
); } @@ -486,3 +579,113 @@ export class FilterPanel extends ObservableReactComponent { return undefined; } } + + + +interface HotKeyButtonProps { + hotKey: string; + selected?: Doc +} + +const HotKeyIconButton: React.FC = observer(({ hotKey, selected}) => { + const state = useLocalObservable(() => ({ + isActive: false, + toggleActive() { + this.isActive = !this.isActive; + }, + deactivate() { + this.isActive = false; + } + })); + + const panelRef = useRef(null); + + const handleClick = () => { + state.toggleActive(); + + console.log(state.isActive + "hmmm") + }; + + const handleClickOutside = (event: MouseEvent) => { + if (panelRef.current && !panelRef.current.contains(event.target as Node)) { + state.deactivate(); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const iconOpts = ['star', 'heart', 'bolt']; + + const iconPanel = iconOpts.map((icon, i) => ( + + )); + + const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles) + + return ( + <> + +
{ + + // if (e.target === e.currentTarget){ + isAttrFiltered(hotKey) ? Doc.setDocFilter(selected, hotKey, true, 'remove') : Doc.setDocFilter(selected, hotKey, true, 'match'); + // } + }, hotKey)} + + // style = {{backgroundColor: `${isAttrFiltered(hotKey) ? '#878484' : SettingsManager.userBackgroundColor}`}} + + + > +
+ Click to customize this hotkey's icon
}> + + + {state.isActive && ( +
+ {iconPanel} +
+ )} + + +
+ +

{hotKey.toUpperCase()}

+ + + + + + + + + + + ); +}); + + diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f552540b8..9da359e1b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -551,7 +551,8 @@ export class MainView extends ObservableReactComponent<{}> { fa.faStar, fa.faCloud, fa.faBolt, - fa.faLightbulb + fa.faLightbulb, + fa.faX ] ); } diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx index 9a2273c3a..73360b684 100644 --- a/src/client/views/nodes/IconTagBox.tsx +++ b/src/client/views/nodes/IconTagBox.tsx @@ -41,24 +41,24 @@ export class IconTagBox extends ObservableReactComponent { this.ref = React.createRef(); } - componentDidMount(): void { - this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; - this._props.doc._keywordHeight = this.height; - - reaction( - () => this.currentScale, - () => { - if (this.currentScale < 1) { - this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; - this._props.doc._keywordHeight = this.height; - } - } - ); - } + // componentDidMount(): void { + // this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + // this._props.doc._keywordHeight = this.height; + + // reaction( + // () => this.currentScale, + // () => { + // if (this.currentScale < 1) { + // this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + // this._props.doc._keywordHeight = this.height; + // } + // } + // ); + // } componentDidUpdate(prevProps: Readonly): void { - this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; - this._props.doc._keywordHeight = this.height; + // this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0; + this._props.doc[DocData].tagHeight = 36*this.currentScale; } /** @@ -93,12 +93,16 @@ export class IconTagBox extends ObservableReactComponent { 3: 'bolt' }; + + return (
Date: Fri, 6 Sep 2024 13:57:03 -0400 Subject: removed calendar button. added metadata value input via tagsview --- src/client/views/DocumentButtonBar.tsx | 18 ------------------ src/client/views/TagsView.tsx | 15 +++++++++------ 2 files changed, 9 insertions(+), 24 deletions(-) (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 58b7f207c..437ef045f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -263,23 +263,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( ); } - @computed - get calendarButton() { - const targetDoc = this.view0?.Document; - return !targetDoc ? null : ( - Open calendar menu
}> -
{ - CalendarManager.Instance.open(this.view0, targetDoc); - }}> - -
- - ); - } - @computed get keywordButton() { return !DocumentView.Selected().length ? null : ( @@ -460,7 +443,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( {!DocumentView.Selected().some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{this.recordButton}
-
{this.calendarButton}
{this.keywordButton}
{!Doc.UserDoc().documentLinksButton_fullMenu ? null :
{this.shareButton}
}
{this.menuButton}
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 7723da900..2d583d392 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -283,15 +283,18 @@ export class TagsView extends ObservableReactComponent { }; /** - * Adds the specified tag to the Doc. If the tag is not prefixed with '#', then a '#' prefix is added. - * Whne the tag (after the '#') begins with '@', then a metadata key/value pair is displayed instead of - * just the tag. - * @param tag tag string to add + * Adds the specified tag or metadata to the Doc. If the tag is not prefixed with '#', then a '#' prefix is added. + * When the tag (after the '#') begins with '@', then a metadata key/value pair is displayed instead of + * just the tag. In addition, a suffix of : can be added to set a metadata value + * @param tag tag string to add (format: # | #@field(:(=)?value)? ) */ submitTag = undoable( action((tag: string) => { - const submittedLabel = tag.trim(); - submittedLabel && TagItem.addTagToDoc(this._props.View.Document, '#' + submittedLabel.replace(/^#/, '')); + const submittedLabel = tag.trim().replace(/^#/, '').split(':'); + if (submittedLabel[0]) { + TagItem.addTagToDoc(this._props.View.Document, '#' + submittedLabel[0]); + if (submittedLabel.length > 1) Doc.SetField(this._props.View.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]); + } this._currentInput = ''; // Clear the input box }), 'added doc label' -- cgit v1.2.3-70-g09d2 From 623da0b4eec34fbd238cc26a5e0c105426a6711e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 12 Sep 2024 09:50:31 -0400 Subject: added tags for multi-selections. --- src/client/views/DocumentButtonBar.tsx | 10 +++- src/client/views/DocumentDecorations.scss | 7 +++ src/client/views/DocumentDecorations.tsx | 11 +++- src/client/views/StyleProvider.tsx | 2 +- src/client/views/TagsView.tsx | 65 ++++++++++++++-------- .../collectionFreeForm/FaceCollectionBox.tsx | 2 +- 6 files changed, 69 insertions(+), 28 deletions(-) (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 437ef045f..785e69f84 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -267,7 +267,15 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( get keywordButton() { return !DocumentView.Selected().length ? null : ( Open keyword menu}> -
DocumentView.Selected().map(dv => (dv.dataDoc.showTags = !dv.dataDoc.showTags))}> +
{ + const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags); + DocumentView.Selected().forEach(dv => { + dv.dataDoc.showTags = !showing; + }); + }}>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 67e1054c3..346df10d5 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -38,6 +38,13 @@ $resizeHandler: 8px; background: green; border-radius: 50%; } + .documentDecorations-tagsView { + position: absolute; + height: 100%; + pointer-events: all; + border-radius: 50%; + color: black; + } } .documentDecorations-container { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index da35459bb..118a5bd3d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -35,6 +35,7 @@ import { DocumentView } from './nodes/DocumentView'; import { ImageBox } from './nodes/ImageBox'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { TagsView } from './TagsView'; interface DocumentDecorationsProps { PanelWidth: number; @@ -835,12 +836,20 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} />
)} +
+ {DocumentView.Selected().length > 1 ? : null} +
{useRotation && ( diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 513953d17..262f888fb 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -365,7 +365,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); }; - const tags = () => props?.DocumentView?.() ? : null; + const tags = () => props?.DocumentView?.() ? : null; return ( <> {paint()} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 8047363d9..ae9d42749 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -33,7 +33,7 @@ import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; */ interface TagItemProps { - doc: Doc; + docs: Doc[]; tag: string; tagDoc: Opt; showRemoveUI: boolean; @@ -207,8 +207,12 @@ export class TagItem extends ObservableReactComponent { e.preventDefault(); }; + @computed get doc() { + return this._props.docs.lastElement(); + } + render() { - this._props.tagDoc && setTimeout(() => TagItem.addTagToDoc(this._props.doc, this._props.tag)); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection + this._props.tagDoc && setTimeout(() => this._props.docs.forEach(doc => TagItem.addTagToDoc(doc, this._props.tag))); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection const tag = this._props.tag.replace(/^#/, ''); const metadata = tag.startsWith('@') ? tag.replace(/^@/, '') : ''; return ( @@ -216,16 +220,16 @@ export class TagItem extends ObservableReactComponent { {metadata ? ( {tag}  - {typeof this._props.doc[metadata] === 'boolean' ? ( + {typeof this.doc[metadata] === 'boolean' ? ( e.stopPropagation()} onPointerDown={e => e.stopPropagation()} - onChange={undoable(e => (this._props.doc[metadata] = !this._props.doc[metadata]), 'metadata toggle')} - checked={this._props.doc[metadata] as boolean} + onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')} + checked={this.doc[metadata] as boolean} /> ) : ( - Field.toString(this._props.doc[metadata]) + Field.toString(this.doc[metadata]) )} ) : ( @@ -234,7 +238,7 @@ export class TagItem extends ObservableReactComponent { {this.props.showRemoveUI && this._props.tagDoc && ( TagItem.removeTagFromDoc(this._props.doc, this._props.tag, this._props.tagDoc), `remove tag ${this._props.tag}`)} + onPointerDown={undoable(() => this._props.docs.forEach(doc => TagItem.removeTagFromDoc(doc, this._props.tag, this._props.tagDoc)), `remove tag ${this._props.tag}`)} icon={} style={{ width: '8px', height: '8px', marginLeft: '10px' }} /> @@ -245,7 +249,7 @@ export class TagItem extends ObservableReactComponent { } interface TagViewProps { - View: DocumentView; + Views: DocumentView[]; } /** @@ -261,12 +265,12 @@ export class TagsView extends ObservableReactComponent { @observable _panelHeightDirty = 0; @observable _currentInput = ''; - @observable _isEditing = !StrListCast(this._props.View.dataDoc.tags).length; + @observable _isEditing = !StrListCast(this.View.dataDoc.tags).length; _heightDisposer: IReactionDisposer | undefined; componentDidMount() { this._heightDisposer = reaction( - () => this._props.View.screenToContentsTransform(), + () => this.View.screenToContentsTransform(), xf => { this._panelHeightDirty = this._panelHeightDirty + 1; } @@ -276,11 +280,15 @@ export class TagsView extends ObservableReactComponent { this._heightDisposer?.(); } + @computed get View() { + return this._props.Views.lastElement(); + } + @computed get currentScale() { - return Math.max(1, 1 / this._props.View.screenToLocalScale()); + return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale()); } @computed get isEditing() { - return this._isEditing && DocumentView.SelectedDocs().includes(this._props.View.Document); + return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document)); } /** @@ -290,7 +298,7 @@ export class TagsView extends ObservableReactComponent { @action setToEditing = (editing = true) => { this._isEditing = editing; - editing && this._props.View.select(false); + editing && this._props.Views.length === 1 && this.View.select(false); }; /** @@ -303,8 +311,10 @@ export class TagsView extends ObservableReactComponent { action((tag: string) => { const submittedLabel = tag.trim().replace(/^#/, '').split(':'); if (submittedLabel[0]) { - TagItem.addTagToDoc(this._props.View.Document, '#' + submittedLabel[0]); - if (submittedLabel.length > 1) Doc.SetField(this._props.View.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]); + this._props.Views.forEach(view => { + TagItem.addTagToDoc(view.Document, '#' + submittedLabel[0]); + if (submittedLabel.length > 1) Doc.SetField(view.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]); + }); } this._currentInput = ''; // Clear the input box }), @@ -316,20 +326,20 @@ export class TagsView extends ObservableReactComponent { * When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed. */ render() { - const tagsList = new Set(StrListCast(this._props.View.dataDoc.tags)); - const chatTagsList = new Set(StrListCast(this._props.View.dataDoc.tags_chat)); + const tagsList = new Set(StrListCast(this.View.dataDoc.tags)); + const chatTagsList = new Set(StrListCast(this.View.dataDoc.tags_chat)); const facesList = new Set( - DocListCast(this._props.View.dataDoc[Doc.LayoutFieldKey(this._props.View.Document) + '_annotations']) - .concat(this._props.View.Document) + DocListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_annotations']) + .concat(this.View.Document) .filter(d => d.face) .map(doc => StrCast(DocCast(doc.face)?.title)) ); this._panelHeightDirty; - return !this._props.View.Document.showTags ? null : ( + return !this.View.Document.showTags && this._props.Views.length === 1 ? null : (
r && new ResizeObserver(action(() => (this._props.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} + ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} style={{ transformOrigin: 'top left', maxWidth: `${100 * this.currentScale}%`, @@ -338,7 +348,7 @@ export class TagsView extends ObservableReactComponent { backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT, borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT, position: 'relative', - top: `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`, + top: this._props.Views.length > 1 ? 25 : `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`, }}>
@@ -346,10 +356,17 @@ export class TagsView extends ObservableReactComponent { this.setToEditing(!this._isEditing)} icon={} /> )} {Array.from(tagsList).map((tag, i) => ( - + view.Document)} + tag={tag} + tagDoc={TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag)} + setToEditing={this.setToEditing} + showRemoveUI={this.isEditing} + /> ))} {Array.from(facesList).map((tag, i) => ( - + view.Document)} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} /> ))}
{this.isEditing ? ( diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index f9f6c81ab..534f67927 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -179,7 +179,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent() { ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); })}> {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => { - const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.'); + const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url.href.split('.') ?? ['-missing-', '.png']; return (
Date: Thu, 12 Sep 2024 14:42:10 -0400 Subject: changed closing of face rectangles to hide them. added Shift+click of tags button to show face rectangles. --- src/client/documents/Documents.ts | 1 + src/client/views/DocumentButtonBar.tsx | 14 +++++++++----- src/client/views/DocumentDecorations.tsx | 7 ++++--- src/client/views/TagsView.tsx | 6 +++--- .../views/collections/collectionFreeForm/ImageLabelBox.tsx | 4 ++-- src/client/views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/search/FaceRecognitionHandler.tsx | 3 +++ 7 files changed, 23 insertions(+), 14 deletions(-) (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fbdf361dd..89356072a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -273,6 +273,7 @@ export class DocumentOptions { _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix) _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts'); _layout_showCaption?: string; // which field to display in the caption area. leave empty to have no caption + _layout_showTags?: BOOLt = new BoolInfo('whether to show the list of document tags at the bottom of a DocView'); _chromeHidden?: BOOLt = new BoolInfo('whether the editing chrome for a document is hidden'); hideClickBehaviors?: BOOLt = new BoolInfo('whether to hide click behaviors in context menu'); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 785e69f84..f14fd033b 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FaEdit } from 'react-icons/fa'; import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc } from '../../fields/Doc'; +import { Doc, DocListCast } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; @@ -270,12 +270,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
{ - const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags); + onClick={undoable(e => { + const showing = DocumentView.Selected().some(dv => dv.layoutDoc._layout_showTags); DocumentView.Selected().forEach(dv => { - dv.dataDoc.showTags = !showing; + dv.layoutDoc._layout_showTags = !showing; + if (e.shiftKey) + DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => { + if (doc.face) doc.hidden = showing; + }); }); - }}> + }, 'show Doc tags')}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 118a5bd3d..907bb2614 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -232,7 +232,8 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`, + top: DocumentView.Selected().length > 1 ? 0 : `${doc._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} /> @@ -845,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent {DocumentView.Selected().length > 1 ? : null} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index ae9d42749..0b303f6f5 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -172,12 +172,12 @@ export class TagItem extends ObservableReactComponent { docData.data = new List(newEmbeddings); docData.title = this._props.tag; docData.tags = new List([this._props.tag]); - docData.showTags = true; docData.freeform_fitContentsToBox = true; doc._freeform_panX = doc._freeform_panY = 0; doc._width = 900; doc._height = 900; doc.layout_fitWidth = true; + doc._layout_showTags = true; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); newEmbeddings.forEach(embed => Doc.SetContainer(embed, newCollection)); @@ -322,7 +322,7 @@ export class TagsView extends ObservableReactComponent { ); /** - * When 'showTags' is set on a Doc, this displays a wrapping panel of tagItemViews corresponding to all the tags set on the Doc). + * When 'layout_showTags' is set on a Doc, this displays a wrapping panel of tagItemViews corresponding to all the tags set on the Doc). * When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed. */ render() { @@ -336,7 +336,7 @@ export class TagsView extends ObservableReactComponent { ); this._panelHeightDirty; - return !this.View.Document.showTags && this._props.Views.length === 1 ? null : ( + return !this.View.Document._layout_showTags && this._props.Views.length === 1 ? null : (
r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx index e419e522c..033d1590d 100644 --- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx +++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx @@ -139,9 +139,9 @@ export class ImageLabelBox extends ViewBoxBaseComponent() { toggleDisplayInformation = () => { this._displayImageInformation = !this._displayImageInformation; if (this._displayImageInformation) { - this._selectedImages.forEach(doc => (doc[DocData].showTags = true)); + this._selectedImages.forEach(doc => (doc._layout_showTags = true)); } else { - this._selectedImages.forEach(doc => (doc[DocData].showTags = false)); + this._selectedImages.forEach(doc => (doc._layout_showTags = false)); } }; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index e0d6c7c05..0ef67b4be 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -404,7 +404,7 @@ export class RichTextRules { if (!tags.includes(tag)) { tags.push(tag); this.Document[DocData].tags = new List(tags); - this.Document[DocData].showTags = true; + this.Document._layout_showTags = true; } const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); return state.tr diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index 636a44984..c507e54b6 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -220,6 +220,7 @@ export class FaceRecognitionHandler { .then(imgDocFaceDescriptions => { // For each face detected, find a match. const annos = [] as Doc[]; const scale = NumCast(imgDoc.data_nativeWidth) / img.width; + const showTags= imgDocFaceDescriptions.length > 1; imgDocFaceDescriptions.forEach((fd, i) => { const faceDescriptor = new List(Array.from(fd.descriptor)); const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard); @@ -233,12 +234,14 @@ export class FaceRecognitionHandler { y: fd.alignedRect.box.top * scale, _width: fd.alignedRect.box.width * scale, _height: fd.alignedRect.box.height * scale, + _layout_showTags: showTags }) FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, matchedUniqueFace); // add image/faceDescriptor to matched unique face annos.push(faceAnno); }); imgDoc[DocData].data_annotations = new List(annos); + imgDoc._layout_showTags = annos.length > 0; return imgDocFaceDescriptions; }) ); // prettier-ignore -- cgit v1.2.3-70-g09d2 From 64635a0a0e07a107c4019e366feb2fe5b01c8b04 Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Tue, 17 Sep 2024 13:43:40 -0400 Subject: ready to pull --- package-lock.json | 153 ++++++++++++--------- src/client/documents/Documents.ts | 7 - src/client/views/DocumentButtonBar.tsx | 54 +++++--- src/client/views/DocumentDecorations.tsx | 4 - src/client/views/StyleProvider.tsx | 3 +- .../views/collections/CollectionCardDeckView.scss | 58 -------- .../views/collections/CollectionCardDeckView.tsx | 19 +-- src/client/views/global/globalScripts.ts | 12 +- src/client/views/nodes/IconTagBox.scss | 20 +-- src/client/views/pdf/GPTPopup/GPTPopup.scss | 12 +- 10 files changed, 145 insertions(+), 197 deletions(-) (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/package-lock.json b/package-lock.json index fabc6d90a..d20aac0c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -94,7 +94,7 @@ "D": "^1.0.0", "d3": "^7.8.5", "depcheck": "^1.4.7", - "dotenv": "^16.3.1", + "dotenv": "^16.4.5", "eslint-webpack-plugin": "^4.1.0", "exif": "^0.6.0", "exifr": "^7.1.3", @@ -133,6 +133,7 @@ "image-data-uri": "^2.0.1", "image-size": "^1.0.2", "image-size-stream": "^1.1.0", + "install": "^0.13.0", "is-plain-obj": "^4.1.0", "jimp": "^1.0.4", "jpeg-autorotate": "^9.0.0", @@ -157,7 +158,7 @@ "node-stream-zip": "^1.15.0", "nodemailer": "^6.9.7", "nodemon": "^3.0.2", - "npm": "^10.2.5", + "npm": "^10.8.1", "openai": "^4.26.0", "p-limit": "^6.1.0", "passport": "^0.7.0", @@ -191,6 +192,7 @@ "react-compound-slider": "^3.4.0", "react-datepicker": "^7.3.0", "react-dom": "^18.2.0", + "react-draggable": "^4.4.6", "react-grid-layout": "^1.4.4", "react-icons": "^5.0.1", "react-jsx-parser": "^2.0.0", @@ -249,7 +251,7 @@ "xregexp": "^5.1.1" }, "devDependencies": { - "@eslint/js": "^9.1.1", + "@eslint/js": "^9.10.0", "@types/adm-zip": "^0.5.5", "@types/animejs": "^3.1.12", "@types/archiver": "^6.0.2", @@ -298,9 +300,9 @@ "@types/youtube": "^0.1.0", "chai": "^5.0.0", "cross-env": "^7.0.3", - "eslint": "^9.9.0", - "eslint-plugin-react": "^7.34.1", - "globals": "^15.1.0", + "eslint": "^9.10.0", + "eslint-plugin-react": "^7.36.1", + "globals": "^15.9.0", "jsdom": "^25.0.0", "mocha": "^10.2.0", "prettier": "^3.1.0", @@ -309,7 +311,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", - "typescript-eslint": "^8.2.0", + "typescript-eslint": "^8.6.0", "webpack-dev-server": "^5.0.4" }, "engines": { @@ -2698,9 +2700,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", - "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -2713,6 +2715,17 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@ffmpeg/core": { "version": "0.12.6", "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.12.6.tgz", @@ -10064,16 +10077,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz", - "integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", + "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/type-utils": "8.4.0", - "@typescript-eslint/utils": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -10097,15 +10110,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.4.0.tgz", - "integrity": "sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz", + "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/typescript-estree": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4" }, "engines": { @@ -10125,13 +10138,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz", - "integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz", + "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0" + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10142,13 +10155,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz", - "integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz", + "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.4.0", - "@typescript-eslint/utils": "8.4.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -10166,9 +10179,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", - "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10179,13 +10192,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz", - "integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz", + "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -10234,15 +10247,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz", - "integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz", + "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/typescript-estree": "8.4.0" + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10256,12 +10269,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz", - "integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz", + "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/types": "8.6.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -17701,15 +17714,16 @@ } }, "node_modules/eslint": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz", - "integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.9.1", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", @@ -17732,7 +17746,6 @@ "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", @@ -17759,9 +17772,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.35.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz", - "integrity": "sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ==", + "version": "7.36.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz", + "integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==", "dev": true, "dependencies": { "array-includes": "^3.1.8", @@ -20914,6 +20927,14 @@ "inspect-function": "^0.3.1" } }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -32710,14 +32731,14 @@ "integrity": "sha512-7sI4e/bZijOzyURng88oOFZCISQPTHozfE2sUu5AviFYk5QV7fYGb6YiDl+vKjF/pICA354JImBImL9XJWUvdQ==" }, "node_modules/typescript-eslint": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.4.0.tgz", - "integrity": "sha512-67qoc3zQZe3CAkO0ua17+7aCLI0dU+sSQd1eKPGq06QE4rfQjstVXR6woHO5qQvGUa550NfGckT4tzh3b3c8Pw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.6.0.tgz", + "integrity": "sha512-eEhhlxCEpCd4helh3AO1hk0UP2MvbRi9CtIAJTVPQjuSXOOO2jsEacNi4UdcJzZJbeuVg1gMhtZ8UYb+NFYPrA==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.4.0", - "@typescript-eslint/parser": "8.4.0", - "@typescript-eslint/utils": "8.4.0" + "@typescript-eslint/eslint-plugin": "8.6.0", + "@typescript-eslint/parser": "8.6.0", + "@typescript-eslint/utils": "8.6.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 623a5251e..aac15059e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -488,14 +488,7 @@ export class DocumentOptions { userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); cardSort?: STRt = new StrInfo('way cards are sorted in deck view'); - // cardSortForDropDown?: STRt = new StrInfo('needed for dropdown and i dont know why') - // cardSort_customField?: STRt = new StrInfo('field key used for sorting cards'); - cardSort_activeIcons?: List; //icons each card is tagges with - // cardSort_visibleSortGroups?: List; // which sorting values are being filtered (shown) - - // cardSort_visibleSortGroups?: List; // which sorting values are being filtered (shown) cardSort_isDesc?: BOOLt = new BoolInfo('whether the cards are sorted ascending or descending'); - // test?: STRt = new StrInfo('testing for filtering') } export const DocOptions = new DocumentOptions(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index ea859a1e5..ccde1de80 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FaEdit } from 'react-icons/fa'; import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, DocListCast } from '../../fields/Doc'; +import { Doc } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; @@ -263,6 +263,26 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( ); } + @computed + get calendarButton() { + const targetDoc = this.view0?.Document; + return !targetDoc ? null : ( + Open calendar menu
}> +
{ + CalendarManager.Instance.open(this.view0, targetDoc); + }}> + +
+ + ); + } + + /** + * Allows for both the keywords and the icon tags to be shown using a quasi- multitoggle + */ @computed get keywordButton() { const targetDoc = this.view0?.Document; @@ -279,21 +299,10 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( size="sm" icon={icon} onClick={e => { - // console.log('wtfff') - // name === 'tags' ?? if (name === 'tags'){ - undoable(e => (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags)), 'show icon tags') + (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags)) } else { - undoable(e => { - const showing = DocumentView.Selected().some(dv => dv.layoutDoc._layout_showTags); - DocumentView.Selected().forEach(dv => { - dv.layoutDoc._layout_showTags = !showing; - if (e.shiftKey) - DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => { - if (doc.face) doc.hidden = showing; - }); - }); - }, 'show Doc tags') + (targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels)) } @@ -309,15 +318,23 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( - return !DocumentView.Selected().length ? null : ( + return !targetDoc ? null : (
{metaBtn('tags', 'star')} {metaBtn("keywords", 'id-card')}
- Open keyword / icon tag menu
}> -
+ Open keyword menu
}> +
{ + // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags; + }} + > + +
@@ -495,10 +512,11 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( {!DocumentView.Selected().some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{this.recordButton}
+
{this.calendarButton}
{this.keywordButton}
{!Doc.UserDoc().documentLinksButton_fullMenu ? null :
{this.shareButton}
}
{this.menuButton}
); } -} +} \ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 45ce681aa..37b1f04e7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -773,10 +773,6 @@ export class DocumentDecorations extends ObservableReactComponent CollectionFreeFormDocumentView.from(v)); - const keyWordTrans = doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) : 0 - const tagTrans = doc[DocData].showIconTags ? NumCast(doc[DocData].tagHeight) : 0 - - return (
, props: Opt ); }; - const tags = () => props?.DocumentView?.() && CollectionFreeFormDocumentView.from(props.DocumentView()) ? : null; + const tags = () => props?.DocumentView?.() ? : null; const iconTags = () => { if (doc && doc![DocData].showIconTags) {return ()} } - return ( <> {paint()} diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index 1728f7aee..5ccc3d9a8 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -12,7 +12,6 @@ height: 35px; border-radius: 50%; background-color: $dark-gray; - // border-color: $medium-blue; margin: 5px; // transform: translateY(-50px); } } @@ -20,7 +19,6 @@ .card-wrapper { display: grid; grid-template-columns: repeat(10, 1fr); - // width: 100%; transform-origin: top left; position: absolute; @@ -31,33 +29,6 @@ transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); } -// .card-button-container { -// display: flex; -// padding: 3px; -// // width: 300px; -// // height:100px; -// pointer-events: none; /* This ensures the container does not capture hover events */ - -// background-color: rgb(218, 218, 218); /* Background color of the container */ -// border-radius: 50px; /* Rounds the corners of the container */ -// transform: translateY(25px); -// // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ -// align-items: center; /* Centers buttons vertically */ -// justify-content: start; /* Centers buttons horizontally */ - -// button { -// pointer-events: auto; /* Re-enable pointer events for the buttons */ - -// width: 70px; -// height: 70px; -// border-radius: 50%; -// background-color: $dark-gray; -// // border-color: $medium-blue; -// margin: 5px; // transform: translateY(-50px); -// background-color: transparent; -// } -// } - .no-card-span{ position: relative; width: fit-content; @@ -70,28 +41,6 @@ -// button:hover { -// transform: translateY(-50px); -// } - -// .card-wrapper::after { -// content: ""; -// width: 100%; /* Forces wrapping */ -// } - -// .card-wrapper > .card-item:nth-child(10n)::after { -// content: ""; -// width: 100%; /* Forces wrapping after every 10th item */ -// } - -// .card-row{ -// display: flex; -// position: absolute; -// align-items: center; -// transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); - -// } - .card-item-inactive, .card-item-active, .card-item { @@ -101,18 +50,11 @@ flex-direction: column; } -// .card-item:hover { -// box-shadow: 0 20px 20px $medium-blue; -// transform: scale(1.05); - - -// } .card-item-inactive { opacity: 0.5; } .card-item-active { - // position: absolute; z-index: 100; } diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 3d9f28a49..bb932b8b2 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,9 +1,9 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils'; -import { emptyFunction, numberRange } from '../../../Utils'; -import { Doc, NumListCast, StrListCast } from '../../../fields/Doc'; +import { ClientUtils, returnFalse, returnZero } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; +import { Doc, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { BoolCast, Cast, DateCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types'; @@ -144,18 +144,7 @@ export class CollectionCardView extends CollectionSubView() { * custom group */ @computed get childDocsWithoutLinks() { - const regularDocs = this.childDocs.filter(l => l.type !== DocumentType.LINK); - const activeGroups = StrListCast(this.Document.cardSort_visibleSortGroups); - - if (activeGroups.length > 0) { - return regularDocs.filter(doc => { - const activeTags = StrListCast(doc.cardSort_activeIcons); - return activeTags !== undefined && activeTags.some(tag => activeGroups.includes(tag)); - }); - } - - // Default return for non-custom cardSort or other cases, filtering out links - return regularDocs; + return this.childDocs.filter(l => l.type !== DocumentType.LINK); } /** diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 53e10d44d..65bd524fe 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -147,7 +147,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'hcenter' | 'vcenter' | 'grid' } // prettier-ignore - const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'tag', + const map: Map<'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'chat' | 'up' | 'down' | 'pile' | 'toggle-chat' | 'tag', { waitForRender?: boolean; checkResult: (doc: Doc) => boolean; @@ -169,9 +169,13 @@ ScriptingGlobals.add(function showFreeform(attr: 'hcenter' | 'vcenter' | 'grid' else (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce(); }, }], - ['center', { - checkResult: (doc: Doc) => BoolCast(doc?._stacking_alignCenter, false), - setDoc: (doc: Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; }, + ['vcenter', { + checkResult: (doc:Doc) => !StrCast(doc?._layout_dontCenter).includes('y'), + setDoc: (doc:Doc) => { doc._layout_dontCenter = StrCast(doc.layout_dontCenter).includes('y') ? StrCast(doc.layout_dontCenter).replace(/y/,"") : StrCast(doc.layout_dontCenter) + 'y'; }, + }], + ['hcenter', { + checkResult: (doc:Doc) => !StrCast(doc?._layout_dontCenter).includes('x'), + setDoc: (doc:Doc) => { doc._layout_dontCenter = StrCast(doc.layout_dontCenter).includes('x') ? StrCast(doc.layout_dontCenter).replace(/x/,"") : 'x'+ StrCast(doc.layout_dontCenter); }, }], ['clusters', { waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss index 8c0f92c90..211a961c1 100644 --- a/src/client/views/nodes/IconTagBox.scss +++ b/src/client/views/nodes/IconTagBox.scss @@ -4,27 +4,21 @@ display: flex; padding: 3px; position: absolute; - // width: 300px; - // height:100px; - pointer-events: none; /* This ensures the container does not capture hover events */ - - background-color: rgb(218, 218, 218); /* Background color of the container */ - border-radius: 50px; /* Rounds the corners of the container */ + pointer-events: none; + background-color: rgb(218, 218, 218); + border-radius: 50px; transform: translateY(25px); - // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */ - align-items: center; /* Centers buttons vertically */ - justify-content: start; /* Centers buttons horizontally */ + align-items: center; + justify-content: start; button { - pointer-events: auto; /* Re-enable pointer events for the buttons */ + pointer-events: auto; transform: translateY(-7.5px); - width: 30px; height: 30px; border-radius: 50%; background-color: $dark-gray; - // border-color: $medium-blue; - margin: 5px; // transform: translateY(-50px); + margin: 5px; background-color: transparent; } } \ No newline at end of file diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss index 1defd1a7f..0247dc10c 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.scss +++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss @@ -104,9 +104,9 @@ $highlightedText: #82e0ff; display: flex; flex-direction: column; width: 100%; - max-height: calc(100vh - 80px); /* Height minus the input box and some padding */ + max-height: calc(100vh - 80px); overflow-y: auto; - padding-bottom: 60px; /* Space for the input */ + padding-bottom: 60px; } .chat-bubbles { @@ -150,15 +150,7 @@ $highlightedText: #82e0ff; } - // button { - // font-size: 9px; - // padding: 10px; - // color: #ffffff; - // width: 100%; - // background-color: $button; - // border-radius: 5px; - // } .text-btn { &:hover { -- cgit v1.2.3-70-g09d2 From 4e0ea65c08ff07429cb32f3907268206c80889fa Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 17 Sep 2024 22:11:22 -0400 Subject: linted cardView files after merge. --- src/client/apis/gpt/GPT.ts | 16 +- src/client/views/DocumentButtonBar.tsx | 51 ++--- src/client/views/FilterPanel.tsx | 352 ++++++++++++++++----------------- src/client/views/StyleProvider.tsx | 10 +- src/client/views/nodes/FieldView.tsx | 4 +- 5 files changed, 208 insertions(+), 225 deletions(-) (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 2657bf12e..5be9d84ff 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -1,4 +1,4 @@ -import { ChatCompletionMessageParam } from 'openai/resources'; +import { ChatCompletionMessageParam, Image } from 'openai/resources'; import { openai } from './setup'; enum GPTCallType { @@ -15,7 +15,7 @@ enum GPTCallType { RUBRIC = 'rubric', TYPE = 'type', SUBSET = 'subset', - INFO = 'info' + INFO = 'info', } type GPTCallOpts = { @@ -48,7 +48,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { model: 'gpt-4o', maxTokens: 2048, temp: 0.25, - prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and NO commas" + prompt: "The user is going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Sort them by the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning for the way you sorted (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and NO commas", }, describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' }, chatcard: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Answer the following question as a short flashcard response. Do not include a label.' }, @@ -77,17 +77,15 @@ const callTypeMap: { [type: string]: GPTCallOpts } = { model: 'gpt-4-turbo', maxTokens: 1024, temp: 0, - prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Based on the question the user asks, provide a subset of the given descriptions that best matches the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning in the 2nd person (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and no commas" + prompt: "I'm going to give you a list of descriptions. Each one is separated by `======` on either side. Descriptions will vary in length, so make sure to only separate when you see `======`. Based on the question the user asks, provide a subset of the given descriptions that best matches the user's specifications. Make sure each description is only in the list once. Each item should be separated by `======`. Immediately afterward, surrounded by `------` on BOTH SIDES, provide some insight into your reasoning in the 2nd person (and mention nothing about the formatting details given in this description). It is VERY important that you format it exactly as described, ensuring the proper number of `=` and `-` (6 of each) and no commas", }, info: { model: 'gpt-4-turbo', maxTokens: 1024, temp: 0, - prompt: "Answer the user's question with a short (<100 word) response. If a particular document is selected I will provide that information (which may help with your response)" + prompt: "Answer the user's question with a short (<100 word) response. If a particular document is selected I will provide that information (which may help with your response)", }, - - }; let lastCall = ''; @@ -98,7 +96,7 @@ let lastResp = ''; * @param inputText Text to process * @returns AI Output */ -const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: any) => { +const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: string) => { const inputText = [GPTCallType.SUMMARY, GPTCallType.FLASHCARD, GPTCallType.QUIZ].includes(callType) ? inputTextIn + '.' : inputTextIn; const opts: GPTCallOpts = callTypeMap[callType]; if (lastCall === inputText) return lastResp; @@ -131,7 +129,7 @@ const gptImageCall = async (prompt: string, n?: number) => { n: n ?? 1, size: '1024x1024', }); - return response.data.map((data: any) => data.url); + return response.data.map((data: Image) => data.url); // return response.data.data[0].url; } catch (err) { console.error(err); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index ccde1de80..87dd5f45a 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -298,16 +298,12 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( key={icon.toString()} size="sm" icon={icon} - onClick={e => { - if (name === 'tags'){ - (targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags)) + onClick={() => { + if (name === 'tags') { + targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags); } else { - (targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels)) + targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels); } - - - - }} />
@@ -315,31 +311,24 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( ); }; - - - return !targetDoc ? null : ( -
-
- {metaBtn('tags', 'star')} - {metaBtn("keywords", 'id-card')} -
- - Open keyword menu
}> -
{ - // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags; - }} - > - - - +
+
+ {metaBtn('tags', 'star')} + {metaBtn('keywords', 'id-card')}
- -
+ Open keyword menu
}> +
{ + // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags; + }}> + +
+ +
); } @@ -519,4 +508,4 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
); } -} \ No newline at end of file +} diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 8133a4d0d..3795a4a6d 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable, ObservableMap } from 'mobx'; @@ -24,14 +23,186 @@ import { DocumentView } from './nodes/DocumentView'; import { ButtonType } from './nodes/FontIconBox/FontIconBox'; import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components'; import { ObservableReactComponent } from './ObservableReactComponent'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +interface HotKeyButtonProps { + hotKey: string; + selected?: Doc; +} + +/** + * Renders the buttons that correspond to each icon tag in the properties view. Allows users to change the icon, + * title, and delete. + */ +const HotKeyIconButton: React.FC = observer(({ hotKey /*, selected */ }) => { + const state = useLocalObservable(() => ({ + isActive: false, + isEditing: false, + myHotKey: hotKey, + + toggleActive() { + this.isActive = !this.isActive; + }, + deactivate() { + this.isActive = false; + }, + startEditing() { + this.isEditing = true; + }, + stopEditing() { + this.isEditing = false; + }, + setHotKey(newHotKey: string) { + this.myHotKey = newHotKey; + }, + })); + + const panelRef = useRef(null); + const inputRef = useRef(null); + + const handleClick = () => { + state.toggleActive(); + }; + + const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles); + const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); + const filter = DocCast(buttons.Filter); + + /** + * The doc of the button in the context menu that corresponds to the current hotkey + * @returns + */ + const myHotKeyDoc = () => { + const hotKeyDocs = DocListCast(filter.data); + return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0]; + }; + + /** + * Removes a hotkey from list + */ + const removeHotKey = () => { + Doc.RemoveDocFromList(filter, 'data', myHotKeyDoc()); + }; + + /** + * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new + * hotkey with the current icon + */ + const updateFromInput = undoable(() => { + const myDoc = myHotKeyDoc(); + Doc.UserDoc().myFilterHotKeyTitles = new List(hotKeys.map(k => (k === hotKey ? state.myHotKey : k))); + Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey]); + Doc.UserDoc()[hotKey] = ''; + myDoc.title = state.myHotKey; + myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`; + }, ''); + + /** + * Deselects if the user clicks outside the button + * @param event + */ + const handleClickOutside = (event: MouseEvent) => { + if (panelRef.current && !panelRef.current.contains(event.target as Node)) { + state.deactivate(); + if (state.isEditing) { + state.stopEditing(); + + updateFromInput(); + } + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard'] as IconProp[]; + + /** + * Panel of icons the user can choose from to represent their tag + */ + const iconPanel = iconOpts.map((icon, i) => ( + + )); + + /** + * Actually renders the buttons + */ + + return ( +
{ + e.stopPropagation(); + state.startEditing(); + setTimeout(() => inputRef.current?.focus(), 0); + }}> +
+ Click to customize this hotkey's icon
}> + + + {state.isActive &&
{iconPanel}
} +
+ {state.isEditing ? ( + state.setHotKey(e.target.value)} + onBlur={() => { + state.stopEditing(); + updateFromInput(); + }} + onKeyDown={e => { + if (e.key === 'Enter') { + state.stopEditing(); + updateFromInput(); + } + }} + className="hotkey-title-input" + /> + ) : ( +

{hotKey.toUpperCase()}

+ )} + +
+ ); +}); interface filterProps { Document: Doc; } @observer export class FilterPanel extends ObservableReactComponent { - @observable _selectedFacetHeaders = new Set(); + // eslint-disable-next-line no-use-before-define public static Instance: FilterPanel; constructor(props: filterProps) { @@ -40,6 +211,7 @@ export class FilterPanel extends ObservableReactComponent { FilterPanel.Instance = this; } + @observable _selectedFacetHeaders = new Set(); /** * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection */ @@ -263,8 +435,8 @@ export class FilterPanel extends ObservableReactComponent { const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles); // Selecting a button should make it so that the icon on the top filter panel becomes said icon - const buttons = hotKeys.map((hotKey, i) => ( - Click to customize this hotkey's icon}> + const buttons = hotKeys.map(hotKey => ( + Click to customize this hotkey's icon}> )); @@ -457,175 +629,3 @@ export class FilterPanel extends ObservableReactComponent { return undefined; } } - -interface HotKeyButtonProps { - hotKey: string; - selected?: Doc; -} - -/** - * Renders the buttons that correspond to each icon tag in the properties view. Allows users to change the icon, - * title, and delete. - */ -const HotKeyIconButton: React.FC = observer(({ hotKey, selected }) => { - const state = useLocalObservable(() => ({ - isActive: false, - isEditing: false, - myHotKey: hotKey, - - toggleActive() { - this.isActive = !this.isActive; - }, - deactivate() { - this.isActive = false; - }, - startEditing() { - this.isEditing = true; - }, - stopEditing() { - this.isEditing = false; - }, - setHotKey(newHotKey: string) { - this.myHotKey = newHotKey; - }, - })); - - const panelRef = useRef(null); - const inputRef = useRef(null); - - const handleClick = () => { - state.toggleActive(); - }; - - const hotKeys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles); - const buttons = DocCast(Doc.UserDoc().myContextMenuBtns); - const filter = DocCast(buttons.Filter); - - /** - * The doc of the button in the context menu that corresponds to the current hotkey - * @returns - */ - const myHotKeyDoc = () => { - const hotKeyDocs = DocListCast(filter.data); - return hotKeyDocs.filter(k => StrCast(k.title) === hotKey)[0]; - }; - - /** - * Removes a hotkey from list - */ - const removeHotKey = () => { - Doc.RemoveDocFromList(filter, 'data', myHotKeyDoc()); - }; - - /** - * Deselects if the user clicks outside the button - * @param event - */ - const handleClickOutside = (event: MouseEvent) => { - if (panelRef.current && !panelRef.current.contains(event.target as Node)) { - state.deactivate(); - if (state.isEditing) { - state.stopEditing(); - - updateFromInput(); - } - } - }; - - /** - * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new - * hotkey with the current icon - */ - const updateFromInput = undoable(() => { - const myDoc = myHotKeyDoc(); - Doc.UserDoc().myFilterHotKeyTitles = new List(hotKeys.map(k => (k === hotKey ? state.myHotKey : k))); - Doc.UserDoc()[state.myHotKey] = StrCast(Doc.UserDoc()[hotKey]); - Doc.UserDoc()[hotKey] = ''; - myDoc.title = state.myHotKey; - myDoc.toolTip = `Click to toggle the ${state.myHotKey}'s group's visibility`; - }, ''); - - useEffect(() => { - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); - - const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard']; - - /** - * Panel of icons the user can choose from to represent their tag - */ - const iconPanel = iconOpts.map((icon, i) => ( - - )); - - /** - * Actually renders the buttons - */ - - return ( -
{ - e.stopPropagation(); - state.startEditing(); - setTimeout(() => inputRef.current?.focus(), 0); - }}> -
- Click to customize this hotkey's icon
}> - - - {state.isActive &&
{iconPanel}
} -
- {state.isEditing ? ( - state.setHotKey(e.target.value)} - onBlur={() => { - state.stopEditing(); - updateFromInput(); - }} - onKeyDown={e => { - if (e.key === 'Enter') { - state.stopEditing(); - updateFromInput(); - } - }} - className="hotkey-title-input" - /> - ) : ( -

{hotKey.toUpperCase()}

- )} - - - ); -}); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 13fef101a..eb434db40 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -326,7 +326,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt
} closeOnSelect setSelectedVal={((dvValue: unknown) => { @@ -366,12 +365,9 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); }; - const tags = () => props?.DocumentView?.() ? : null; - - const iconTags = () => { - if (doc && doc![DocData].showIconTags) - {return ()} - } + const tags = () => docView?.() ? : null; + const iconTags = () => doc?.[DocData].showIconTags ? : null; + return ( <> {paint()} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index c269c7bcb..683edba16 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,5 +1,3 @@ -/* eslint-disable react/no-unused-prop-types */ -/* eslint-disable react/require-default-props */ import { Property } from 'csstype'; import { computed } from 'mobx'; import { observer } from 'mobx-react'; @@ -21,6 +19,7 @@ export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt // eslint-disable-next-line no-use-before-define export type StyleProviderFuncType = ( doc: Opt, + // eslint-disable-next-line no-use-before-define props: Opt, property: string ) => @@ -65,6 +64,7 @@ export interface FieldViewSharedProps { containerViewPath?: () => DocumentView[]; fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document isGroupActive?: () => string | undefined; // is this document part of a group that is active + // eslint-disable-next-line no-use-before-define setContentViewBox?: (view: ViewBoxInterface) => void; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox PanelWidth: () => number; PanelHeight: () => number; -- cgit v1.2.3-70-g09d2 From d95730d904612640184ca6fdc00864b0c81b0c0c Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 18 Sep 2024 20:46:38 -0400 Subject: lots of changes to fix dragging cards, integrate iconTags with other tags, sizing docs when selected to fit window, --- eslint.config.mjs | 1 + src/client/util/CurrentUserUtils.ts | 14 +- src/client/util/DragManager.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 44 ++---- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/StyleProvider.tsx | 4 - src/client/views/TagsView.tsx | 20 +-- .../views/collections/CollectionCardDeckView.scss | 8 +- .../views/collections/CollectionCardDeckView.tsx | 152 ++++++++++++--------- .../views/collections/CollectionCarouselView.tsx | 1 - .../views/collections/CollectionStackingView.tsx | 3 - src/client/views/collections/CollectionSubView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 5 + src/client/views/nodes/IconTagBox.scss | 24 ++-- src/client/views/nodes/IconTagBox.tsx | 106 +++++++------- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 + src/client/views/search/FaceRecognitionHandler.tsx | 3 +- 18 files changed, 198 insertions(+), 204 deletions(-) (limited to 'src/client/views/DocumentButtonBar.tsx') diff --git a/eslint.config.mjs b/eslint.config.mjs index 8926afd7c..f7063caa5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -36,6 +36,7 @@ export default [ 'no-multi-assign': 'off', 'no-underscore-dangle': 'off', 'no-nested-ternary': 'off', + 'no-param-reassign': 'error', 'lines-between-class-members': 'off', 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'warn', diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index eee6be937..262916312 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -2,7 +2,7 @@ import { reaction, runInAction } from "mobx"; import * as rp from 'request-promise'; import { ClientUtils, OmitKeys } from "../../ClientUtils"; -import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, FieldType, Opt, StrListCast } from "../../fields/Doc"; import { DocData } from "../../fields/DocSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -688,18 +688,18 @@ pie title Minerals in my tap water static tagGroupTools(): Button[] { if (!Doc.UserDoc().activeDashboard) { - Doc.UserDoc().myFilterHotKeyTitles = new List(['Star', 'Heart', 'Bolt', 'Cloud']); + Doc.UserDoc().myFilterHotKeyTitles = new List(['star', 'heart', 'bolt', 'cloud']); - ['Star', 'Heart', 'Bolt', 'Cloud'].forEach(key => { + StrListCast(Doc.UserDoc().myFilterHotKeyTitles).forEach(key => { Doc.UserDoc()[key] = key.toLowerCase(); }); } // hack: if there's no dashboard, create default filters. otherwise, just make sure that the Options button is preserved return (Doc.UserDoc().activeDashboard ? [] : [ - { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "star", isSystem: false,icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, ]).concat([ { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton, expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}} ]) diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index d55d193cc..81ea840f1 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -29,7 +29,7 @@ import { dropActionType } from './DropActionTypes'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-require-imports const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore /** @@ -101,7 +101,6 @@ export namespace DragManager { // event called when the drag operation results in a drop action export class DropEvent { - // eslint-disable-next-line no-useless-constructor constructor( readonly x: number, readonly y: number, diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 87dd5f45a..32bf67df1 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FaEdit } from 'react-icons/fa'; import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc } from '../../fields/Doc'; +import { Doc, DocListCast } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; @@ -28,7 +28,6 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; import { OpenWhere } from './nodes/OpenWhere'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; -import { DocData } from '../../fields/DocSymbols'; @observer export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> { @@ -287,44 +286,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( get keywordButton() { const targetDoc = this.view0?.Document; - const metaBtn = (name: string, icon: IconProp) => { - const tooltip = `Toggle ${name}`; - return ( - {tooltip}}> -
- { - if (name === 'tags') { - targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags); - } else { - targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels); - } - }} - /> -
-
- ); - }; - return !targetDoc ? null : (
-
+ {/*
{metaBtn('tags', 'star')} {metaBtn('keywords', 'id-card')} -
+
*/} Open keyword menu
}>
{ - // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags; - }}> + onClick={undoable(e => { + const showing = DocumentView.Selected().some(dv => dv.showTags); + DocumentView.Selected().forEach(dv => { + dv.layoutDoc._layout_showTags = !showing; + if (e.shiftKey) + DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => { + if (doc.face) doc.hidden = showing; + }); + }); + }, 'show Doc tags')}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 5e7908725..2ba96c2a9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -837,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, + top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} /> @@ -846,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent {DocumentView.Selected().length > 1 ? : null} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index eb434db40..3545afcee 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -9,7 +9,6 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; import { FaFilter } from 'react-icons/fa'; import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; @@ -22,7 +21,6 @@ import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; -import { IconTagBox } from './nodes/IconTagBox'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; import { TagsView } from './TagsView'; @@ -366,7 +364,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt docView?.() ? : null; - const iconTags = () => doc?.[DocData].showIconTags ? : null; return ( <> @@ -375,7 +372,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); } diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index be2c28185..a678d5580 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent'; import './TagsView.scss'; import { DocumentView } from './nodes/DocumentView'; import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; +import { IconTagBox } from './nodes/IconTagBox'; /** * The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection. @@ -59,7 +60,7 @@ export class TagItem extends ObservableReactComponent { * @param tag tag string * @returns tag collection Doc or undefined */ - public static findTagCollectionDoc = (tag: String) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag); + public static findTagCollectionDoc = (tag: string) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag); /** * Creates a Doc that collects Docs with the specified tag / value @@ -148,7 +149,7 @@ export class TagItem extends ObservableReactComponent { private _ref: React.RefObject; - constructor(props: any) { + constructor(props: TagItemProps) { super(props); makeObservable(this); this._ref = React.createRef(); @@ -225,7 +226,7 @@ export class TagItem extends ObservableReactComponent { type="checkbox" onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()} - onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')} + onChange={undoable(() => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')} checked={this.doc[metadata] as boolean} /> ) : ( @@ -257,7 +258,7 @@ interface TagViewProps { */ @observer export class TagsView extends ObservableReactComponent { - constructor(props: any) { + constructor(props: TagViewProps) { super(props); makeObservable(this); } @@ -271,7 +272,7 @@ export class TagsView extends ObservableReactComponent { componentDidMount() { this._heightDisposer = reaction( () => this.View.screenToContentsTransform(), - xf => { + () => { this._panelHeightDirty = this._panelHeightDirty + 1; } ); @@ -288,7 +289,7 @@ export class TagsView extends ObservableReactComponent { return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale()); } @computed get isEditing() { - return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document)); + return this._isEditing && (this._props.Views.length > 1 || (DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View))); } /** @@ -336,7 +337,7 @@ export class TagsView extends ObservableReactComponent { ); this._panelHeightDirty; - return this.View.ComponentView?.isUnstyledView?.() || (!this.View.Document._layout_showTags && this._props.Views.length === 1) ? null : ( + return this.View.ComponentView?.isUnstyledView?.() || (!this.View.showTags && this._props.Views.length === 1) ? null : (
r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} @@ -352,9 +353,10 @@ export class TagsView extends ObservableReactComponent { }}>
- {!tagsList.size && !facesList.size ? null : ( // + {this._props.Views.length === 1 && !this.View.showTags ? null : ( // this.setToEditing(!this._isEditing)} icon={} /> )} + {Array.from(tagsList).map((tag, i) => ( { />
- {TagItem.AllTagCollectionDocs.map((doc, i) => { + {TagItem.AllTagCollectionDocs.map(doc => { const tag = StrCast(doc.title); return ( - - ))} -
- ); - }; /** * Opens the filter panel in the properties menu @@ -93,13 +50,13 @@ export class IconTagBox extends ObservableReactComponent { }; /** - * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt - * @param childPairIndex * @param buttonID * @param doc */ - toggleButton = undoable((doc: Doc, icon: string) => { - BoolCast(doc[icon]) ? (doc[icon] = false) : (doc[icon] = true); + setIconTag = undoable((icon: string, state: boolean) => { + this._props.Views.forEach(view => { + view.dataDoc[icon] = state; + }); }, 'toggle card tag'); /** @@ -112,10 +69,43 @@ export class IconTagBox extends ObservableReactComponent { const isActive = doc[icon.toString()]; const color = isActive ? '#4476f7' : '#323232'; - return ; + return ; }; + /** + * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups + */ render() { - return <>{this.renderButtons(this._props.doc)}; + const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1; + + const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles); + + const iconMap = (buttonID: number) => { + return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp; + }; + const buttons = numberRange(amButtons - 1) + .filter(i => this._props.IsEditing || this.View.Document[iconMap(i).toString()] || (DocumentView.Selected.length === 1 && this.View.IsSelected)) + .map(i => ( + Click to add/remove this card from the {iconMap(i).toString()} group
}> + + + )); + return !buttons.length ? null : ( +
+ {buttons} +
+ ); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 343e255dc..e0331a422 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -275,6 +275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 1; - imgDocFaceDescriptions.forEach((fd, i) => { + imgDocFaceDescriptions.forEach(fd => { const faceDescriptor = new List(Array.from(fd.descriptor)); const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard); const faceAnno = Docs.Create.FreeformDocument([], { -- cgit v1.2.3-70-g09d2