diff options
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 7 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/FontIconBox/FontIconBox.tsx | 21 | ||||
| -rw-r--r-- | src/client/views/nodes/IconTagBox.scss | 26 | ||||
| -rw-r--r-- | src/client/views/nodes/IconTagBox.tsx | 92 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 16 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/RichTextRules.ts | 2 |
10 files changed, 162 insertions, 16 deletions
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index df127f0d5..8fbcb74cb 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -122,7 +122,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() <FontAwesomeIcon icon="lightbulb" size="xl" /> </div> </Tooltip> - {DocCast(this.Document.embedContainer).type_collection === 'carousel' ? null : ( + {DocCast(this.Document.embedContainer)?.type_collection === CollectionViewType.Carousel ? null : ( <div> <Tooltip title={<div>Create a flashcard pile</div>}> <div style={{ position: 'absolute', bottom: '3px', right: '74px', cursor: 'pointer' }} onPointerDown={e => this.createFlashcardPile([this.Document], false)}> @@ -697,6 +697,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() <DocumentView // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} + showTags={undefined} fitWidth={undefined} ignoreUsePath={layoutString ? true : undefined} renderDepth={this.props.renderDepth + 1} @@ -742,13 +743,13 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() // add text box to each side when comparison box is first created if (!this.dataDoc[this.fieldKey + '_0'] && !this._isEmpty) { const dataSplit = StrCast(this.dataDoc.data).includes('Keyword: ') ? StrCast(this.dataDoc.data).split('Keyword: ') : StrCast(this.dataDoc.data).split('Answer: '); - const newDoc = Docs.Create.TextDocument(dataSplit[1]); + const newDoc = Docs.Create.TextDocument(dataSplit[1], { _layout_autoHeight: true }); this.addDoc(newDoc, this.fieldKey + '_0'); } if (!this.dataDoc[this.fieldKey + '_1'] && !this._isEmpty) { const dataSplit = StrCast(this.dataDoc.data).includes('Keyword: ') ? StrCast(this.dataDoc.data).split('Keyword: ') : StrCast(this.dataDoc.data).split('Answer: '); - const newDoc = Docs.Create.TextDocument(dataSplit[0]); + const newDoc = Docs.Create.TextDocument(dataSplit[0], { _layout_autoHeight: true }); this.addDoc(newDoc, this.fieldKey + '_1'); } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index afc160297..9aa000ba7 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -129,6 +129,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte 'childContentPointerEvents', 'LayoutTemplateString', 'LayoutTemplate', + 'showTags', 'layoutFieldKey', 'dontCenter', 'DataTransition', diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 351fdce79..b85cb22bb 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -67,6 +67,8 @@ export interface DocumentViewProps extends FieldViewSharedProps { hideCaptions?: boolean; contentPointerEvents?: Property.PointerEvents | undefined; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents dontCenter?: 'x' | 'y' | 'xy'; + showTags?: boolean; + hideFilterStatus?: boolean; childHideDecorationTitle?: boolean; childHideResizeHandles?: boolean; childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar. @@ -1128,6 +1130,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { @observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing @observable public TagPanelHeight = 0; + @computed get showTags() { + return this.Document._layout_showTags || this._props.showTags; + } + @computed private get shouldNotScale() { return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.(); } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index b0033ef39..7b652dded 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<number> // eslint-disable-next-line no-use-before-define export type StyleProviderFuncType = ( doc: Opt<Doc>, + // eslint-disable-next-line no-use-before-define props: Opt<FieldViewProps>, 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<FieldViewProps>) => void; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox PanelWidth: () => number; PanelHeight: () => number; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 7a09ad9e2..cb0c4d188 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -192,7 +192,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { } else { text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string; // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - getStyle = (val: string) => ({ fontFamily: val }); + if (this.Document.title === 'Font') getStyle = (val: string) => ({ fontFamily: val }); // bcz: major hack to style the font dropdown items --- needs to become part of the dropdown's metadata } // Get items to place into the list @@ -266,26 +266,31 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string; const items = DocListCast(this.dataDoc.data); - const multiDoc = this.Document; + const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType)); return ( <MultiToggle tooltip={`Toggle ${tooltip}`} type={Type.PRIM} color={color} - onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: multiDoc, value: undefined, _readOnly_: false }))} + multiSelect={true} + onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))} isToggle={script ? true : false} toggleStatus={toggleStatus} //background={SnappingManager.userBackgroundColor} label={this.label} - items={DocListCast(this.dataDoc.data).map(item => ({ + items={items.map(item => ({ icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />, tooltip: StrCast(item.toolTip), val: StrCast(item.toolType), }))} - selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType ?? StrCast(multiDoc.toolType))} - setSelectedVal={(val: string | number) => { - const itemDoc = items.find(item => item.toolType === val); - itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: val, _readOnly_: false }); + selectedItems={selectedItems} + onSelectionChange={(val: (string | number) | (string | number)[], added: boolean) => { + // note: the multitoggle is telling us whether the selection was toggled on or off, but we ignore this since we know the state of all the buttons + // and control it through the selectedItems prop. Therefore, the callback script will have to re-determine the toggle information. + // it would be better to pas the 'added' flag to the callback script, but our script generator from currentUserUtils makes it hard to define + // arbitrary parameter variables (but it could be done as a special case or with additional effort when creating the sript) + const itemsChanged = items.filter(item => (val instanceof Array ? val.includes(item.toolType as string | number) : item.toolType === val)); + itemsChanged.forEach(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, _added_: added, itemDoc, _readOnly_: false })); }} /> ); diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss new file mode 100644 index 000000000..90cc06092 --- /dev/null +++ b/src/client/views/nodes/IconTagBox.scss @@ -0,0 +1,26 @@ +@import '../global/globalCssVariables.module.scss'; + +.card-button-container { + display: flex; + position: relative; + pointer-events: none; + background-color: rgb(218, 218, 218); + border-radius: 50px; + align-items: center; + gap: 5px; + padding-left: 5px; + padding-right: 5px; + padding-top: 2px; + padding-bottom: 2px; + + button { + pointer-events: auto; + width: 20px; + height: 20px; + margin: auto; + padding: 0; + border-radius: 50%; + background-color: $dark-gray; + background-color: transparent; + } +} diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx new file mode 100644 index 000000000..ddabd61e1 --- /dev/null +++ b/src/client/views/nodes/IconTagBox.tsx @@ -0,0 +1,92 @@ +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@mui/material'; +import { computed, makeObservable } from 'mobx'; +import { observer } from 'mobx-react'; +import React from 'react'; +import { returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; +import { emptyFunction } from '../../../Utils'; +import { Doc } from '../../../fields/Doc'; +import { StrCast } from '../../../fields/Types'; +import { undoable } from '../../util/UndoManager'; +import { ObservableReactComponent } from '../ObservableReactComponent'; +import { TagItem } from '../TagsView'; +import { DocumentView } from './DocumentView'; +import './IconTagBox.scss'; + +export interface IconTagProps { + Views: DocumentView[]; + IsEditing: boolean; +} + +/** + * Renders the icon tags that rest under the document. The icons rendered are determined by the values of + * each icon in the userdoc. + */ +@observer +export class IconTagBox extends ObservableReactComponent<IconTagProps> { + constructor(props: IconTagProps) { + super(props); + makeObservable(this); + } + + @computed get View() { return this._props.Views.lastElement(); } // prettier-ignore + @computed get currentScale() { return this.View?.screenToLocalScale(); } // prettier-ignore + + /** + * Sets or removes the specified tag + * @param tag tag name (should begin with '#') + * @param state flag to add or remove the metadata + */ + setIconTag = undoable((tag: string, state: boolean) => { + this._props.Views.forEach(view => { + state && TagItem.addTagToDoc(view.dataDoc, tag); + !state && TagItem.removeTagFromDoc(view.dataDoc, tag); + }); + }, 'toggle card tag'); + + /** + * Returns a renderable version of the button Doc that is colorized to indicate + * whether the doc has the associated tag set on it or not. + * @param doc doc to test + * @param key metadata icon button + * @returns an icon for the metdata button + */ + getButtonIcon = (doc: Doc, key: Doc): JSX.Element => { + const icon = StrCast(key.icon) as IconProp; + const tag = StrCast(key.toolType); + const isActive = TagItem.docHasTag(doc, tag); + const color = isActive ? '#4476f7' : '#323232'; // TODO should use theme colors + + return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />; + }; + + /** + * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups + */ + render() { + const buttons = Doc.MyFilterHotKeys + .map(key => ({ key, tag: StrCast(key.toolType) })) + .filter(({ tag }) => this._props.IsEditing || TagItem.docHasTag(this.View.Document, tag) || (DocumentView.Selected().length === 1 && this.View.IsSelected)) + .map(({ key, tag }) => ( + <Tooltip key={tag} title={<div className="dash-tooltip">Click to add/remove this card from the {tag} group</div>}> + <button + type="button" + onPointerDown={e => + setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => { + this.setIconTag(tag, !TagItem.docHasTag(this.View.Document, tag)); + clickEv.stopPropagation(); + }) + }> + {this.getButtonIcon(this.View.Document, key)} + </button> + </Tooltip> + )); // prettier-ignore + + return !buttons.length ? null : ( + <div className="card-button-container" style={{ fontSize: '50px' }}> + {buttons} + </div> + ); + } +} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 45301247b..91e51d24e 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -693,7 +693,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { TraceMobx(); const backColor = DashColor((this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string) ?? Colors.WHITE); - const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1; + // allow use case where the image is transparent when the alpha value is to smallest possible value from UI (alpha = 1 out of 255) + const backAlpha = backColor.alpha() < 0.015 && backColor.alpha() > 0 ? backColor.alpha() : 1; const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0]; const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement(); const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index a281479f1..07a4d1093 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -213,9 +213,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB return anchor; }; + gptPDFFlashcards = async () => { + const queryText = window.getSelection()?.toString() ?? ''; + try { + if (queryText) { + const res = await gptAPICall(queryText, GPTCallType.FLASHCARD); + AnchorMenu.Instance.transferToFlashcard(res || 'Something went wrong', NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)); + } + } catch (err) { + console.error(err); + } + }; + @action setupAnchorMenu = () => { AnchorMenu.Instance.Status = 'marquee'; + AnchorMenu.Instance.gptFlashcards = this.gptPDFFlashcards; AnchorMenu.Instance.OnClick = () => { !this.layoutDoc.layout_showSidebar && this.toggleSidebar(); setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created @@ -277,6 +290,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB ele.append(contents); } this._selectionHTML = ele?.innerHTML; + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { /* empty */ } @@ -564,7 +578,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const draggedDoc = dragData.droppedDocuments.lastElement(); let added: Opt<boolean>; const dropAction = dragData.dropAction || dragData.userDropAction; - if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) { + if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl) && !dragData.draggedDocuments.includes(this.Document)) { // replace text contents when dragging with Alt if (de.altKey) { const fieldKey = Doc.LayoutFieldKey(draggedDoc); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 0ef67b4be..f58434906 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -406,7 +406,7 @@ export class RichTextRules { this.Document[DocData].tags = new List<string>(tags); this.Document._layout_showTags = true; } - const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); + const fieldView = state.schema.nodes.dashField.create({ fieldKey: tag.startsWith('@') ? tag.replace(/^@/, '') : '#' + tag }); return state.tr .setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))) .replaceSelectionWith(fieldView, true) |
