diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/.DS_Store | bin | 10244 -> 10244 bytes | |||
-rw-r--r-- | src/client/documents/DocumentTypes.ts | 4 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 51 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 14 | ||||
-rw-r--r-- | src/client/views/DocComponent.tsx | 2 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.scss | 34 | ||||
-rw-r--r-- | src/client/views/collections/CollectionCardDeckView.tsx | 389 | ||||
-rw-r--r-- | src/client/views/collections/CollectionMenu.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionView.tsx | 5 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | 34 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 20 | ||||
-rw-r--r-- | src/client/views/global/globalScripts.ts | 135 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 7 |
15 files changed, 646 insertions, 61 deletions
diff --git a/src/.DS_Store b/src/.DS_Store Binary files differindex f8d745dbf..b464c99c8 100644 --- a/src/.DS_Store +++ b/src/.DS_Store diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 1123bcac9..209b34f91 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -62,5 +62,7 @@ export enum CollectionViewType { Pile = 'pileup', StackedTimeline = 'stacked timeline', NoteTaking = 'notetaking', - Calendar = 'calendar' + Calendar = 'calendar', + Card = 'card' + } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b160379df..1c57c5d63 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -498,6 +498,12 @@ export class DocumentOptions { hoverBackgroundColor?: string; // background color of a label when hovered userBackgroundColor?: STRt = new StrInfo('background color associated with a Dash user (seen in header fields of shared documents)'); userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); + + card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time'); + card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type'); + card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color'); + + } export const DocOptions = new DocumentOptions(); @@ -1216,6 +1222,8 @@ export namespace Docs { ); } + + export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Linear }, id); } @@ -1232,6 +1240,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Carousel3D }); } + export function CardDeckDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Card}); + } + export function SchemaDocument(schemaHeaders: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaHeaders: new List(schemaHeaders), ...options, _type_collection: CollectionViewType.Schema }); } @@ -1881,6 +1893,45 @@ export namespace DocUtils { return newCollection; } } + + export function spreadCards(docList: Doc[], x: number = 0, y: number = 0, spreadAngle: number = 30, radius: number = 100, create: boolean = true) { + console.log('spread cards'); + const totalCards = docList.length; + const halfSpreadAngle = spreadAngle * 0.5; + const angleStep = spreadAngle / (totalCards - 1); + + runInAction(() => { + docList.forEach((d, i) => { + DocUtils.iconify(d); + const angle = (-halfSpreadAngle + angleStep * i) * (Math.PI / 180); // Convert degrees to radians + d.x = x + Math.cos(angle) * radius; + d.y = y + Math.sin(angle) * radius; + d.rotation = angle; + d._timecodeToShow = undefined; + }); + }); + + if (create) { + const newCollection = Docs.Create.CardDeckDocument(docList, { + title: 'card-spread', + _freeform_noZoom: true, + x: x - radius, + y: y - radius, + _width: radius * 2, + _height: radius * 2, + dragWhenActive: true, + _layout_fitWidth: false + }); + // Adjust position based on the collection's dimensions if needed + newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - radius; + newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - radius; + newCollection._width = newCollection._height = radius * 2; + return newCollection; + } + } + + + export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) { const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from'); if (!portalLink) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 081115879..88fcf098b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -711,12 +711,23 @@ pie title Minerals in my tap water { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } + static cardTools(): Button[] { + return [ + { title: "Time", icon:"hourglass-half", toolTip:"Sort by most recent document creation", btnType: ButtonType.ClickButton, expertMode: false, toolType:"time", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Type", icon:"eye", toolTip:"Sort by document type", btnType: ButtonType.ClickButton, expertMode: false, toolType:"docType", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + { title: "Color", icon:"palette", toolTip:"Sort by document color", btnType: ButtonType.ClickButton, expertMode: false, toolType:"color", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, + + ] + } static viewTools(): Button[] { return [ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Fit All", icon: "object-group", toolTip: "Fit Docs to View (double click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + ] } static textTools():Button[] { @@ -793,7 +804,7 @@ pie title Minerals in my tap water CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, - CollectionViewType.Grid, CollectionViewType.NoteTaking]), + CollectionViewType.Grid, CollectionViewType.NoteTaking, CollectionViewType.Card]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} }, @@ -808,6 +819,7 @@ pie title Minerals in my tap water { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available + { title: "Card", icon: "Sort", toolTip: "Card sort", subMenu: CurrentUserUtils.cardTools(), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index de4df1830..ef4257937 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -35,7 +35,7 @@ export interface ViewBoxInterface { addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) removeDocument?: (doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean) => boolean; // add a document (used only by collections) select?: (ctrlKey: boolean, shiftKey: boolean) => void; - focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>; + focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>; viewTransition?: () => Opt<string>; // duration of a view transition animation isAnyChildContentActive?: () => boolean; // is any child content of the document active onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 58b8d255a..abdb5abef 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -525,6 +525,8 @@ export class MainView extends ObservableReactComponent<{}> { fa.faZ, fa.faArrowsUpToLine, fa.faArrowsDownToLine, + fa.faPalette, + fa.faHourglassHalf ] ); } diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss new file mode 100644 index 000000000..ff9c0a569 --- /dev/null +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -0,0 +1,34 @@ +@import '../global/globalCssVariables.module.scss'; + +.collectionCardView-outer { + height: 100%; + position: relative; + background-color: white; + overflow: hidden; +} + +.card-wrapper { + display: flex; + position: absolute; + align-items: center; + transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); +} + +.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-active, +.card-item { + transition: transform 0.3s ease-in-out; +} + +.card-item-active { + position: absolute; + z-index: 100; +} diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx new file mode 100644 index 000000000..ab7aac267 --- /dev/null +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -0,0 +1,389 @@ +import { ObservableMap, action, computed, makeObservable, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils'; +import { Doc, DocListCast, Field } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { NumCast, ScriptCast, StrCast, BoolCast } from '../../../fields/Types'; +import { DragManager } from '../../util/DragManager'; +import { SelectionManager } from '../../util/SelectionManager'; +import { StyleProp } from '../StyleProvider'; +import { DocumentView } from '../nodes/DocumentView'; +import './CollectionCardDeckView.scss'; +import { CollectionSubView } from './CollectionSubView'; +import { Transform } from '../../util/Transform'; +// import Card from 'react-bootstrap/Card'; + + +@observer +export class CollectionCardView extends CollectionSubView() { + @observable selectedNodeIndex = -1; + + @observable hoveredNodeIndex = -1; + + @action + setHoveredNodeIndex = (index: number) => { + if (!this.isSelected(index)) { + this.hoveredNodeIndex = index; + } + }; + + translateHover = (index: number): number => { + if (this.hoveredNodeIndex == index && !this.isSelected(index)) { + return -50 * this.fitContentScale; + } + return 0; + }; + + @action + setSelectedNodeIndex = (index: number) => { + const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); + console.log('goodnight'); + + if (SelectionManager.IsSelected(docs[index])) { + console.log('good mornings'); + this.setSelectedNodeIndex(index); + } + }; + + isSelected = (index: number): boolean => { + const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); + + return SelectionManager.IsSelected(docs[index]); + }; + + inactiveDocs = () => { + const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); + + + + return docs.filter(d => !SelectionManager.IsSelected(d)); + }; + + middleIndex = Math.floor(this.inactiveDocs().length / 2); + + constructor(props: any) { + super(props); + makeObservable(this); + // this.rotationDegree(7); + } + + private _dropDisposer?: DragManager.DragDropDisposer; + + componentWillUnmount() { + this._dropDisposer?.(); + } + + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + this._dropDisposer?.(); + if (ele) { + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); + } + }; + + childDocumentWidth = 600; // target width of a Doc... + /** + * how much to scale down the contents of the view so that everything will fit + */ + @computed get fitContentScale() { + return (this.childDocumentWidth * this.childLayoutPairs.length) / this._props.PanelWidth(); + } + panelWidth = () => this.childDocumentWidth; + panelHeight = (layout: Doc) => () => (2 * (this.panelWidth() * NumCast(layout._height))) / NumCast(layout._width); + onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); + isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); + isChildContentActive = () => (this.isContentActive() ? true : false); + + rotate = (amCards: number, index: number) => { + console.log(amCards + "wtf") + const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2)); + const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2))); + + // console.log(possRotate + "poss") + + if (amCards % 2 == 0 && possRotate == 0) { + // console.log('whaddup'); + return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2))); + } else if (amCards % 2 == 0 && index > (amCards + 1) / 2) { + // console.log("sup" + stepMag); + + return possRotate + stepMag; + } + + return possRotate; + }; + + translateY = (amCards: number, index: number) => { + const evenOdd = amCards % 2; + const apex = (amCards - evenOdd) / 2; + const stepMag = 200 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25); + + console.log('steo' + stepMag); + + if (evenOdd == 1 || index < apex - 1) { + console.log('hi' + index); + return Math.abs(stepMag * (apex - index)); + } else { + if (index == apex || index == apex - 1) { + return 0; + } + + return Math.abs(stepMag * (apex - index - 1)); + } + }; + + translateOverFlowY = (amCards: number, index: number) => { + if (amCards > 8 && index > amCards / 2) { + return 100; + } + return 0; + }; + + translateSelected = (index: number): number => { + if (this.isSelected(index)) { + const middleOfPanel = this._props.PanelWidth() / 2; + const scaledNodeWidth = this.panelWidth() * 1.25; + + // Calculate the position of the node's left edge before scaling + const nodeLeftEdge = index * this.panelWidth(); + // Find the center of the node after scaling + const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2; + + // Calculate the translation needed to align the scaled node's center with the panel's center + const translation = middleOfPanel - scaledNodeCenter; + + return translation; + } + + return 0; + }; + + @computed get sortedDocsType() { + + const desc = BoolCast(this.layoutDoc.sortDesc); + + let sorted = [] + + for (let i=0; i< this.childLayoutPairs.length; i++){ //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list + sorted[i] = this.childLayoutPairs[i] + } + + + // Copy and sort documents by type + const docs = sorted.sort((docA, docB) => { + const typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string + const typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string + + // Perform a basic string comparison if types are strings + let out = 0; + if (typeA < typeB) out = -1; + if (typeA > typeB) out = 1; + if (desc) out *= -1; // Reverse the sort order if descending is true + return out; + }); + + + return { docs }; + } + + + + + + @observable docRefs = new ObservableMap<Doc, DocumentView>(); + + @computed get contentSorted() { + const sortedDocs = this.sortedDocsType.docs; + const amCards = this.inactiveDocs().length; + + const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => { + return ( + <DocumentView + {...this._props} + ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))} + Document={childPair.layout} + TemplateDataDocument={childPair.data} + NativeWidth={returnZero} + NativeHeight={returnZero} + layout_fitWidth={returnFalse} + onDoubleClickScript={this.onChildDoubleClick} + renderDepth={this._props.renderDepth + 1} + LayoutTemplate={this._props.childLayoutTemplate} + LayoutTemplateString={this._props.childLayoutString} + ScreenToLocalTransform={screenToLocalTransform} + isContentActive={this.isChildContentActive} + isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight(childPair.layout)} + /> + ); + }; + + // Map sorted documents to their rendered components + return sortedDocs.map((childPair, index) => { + // const childPair = { layout: doc, data: doc }; + const isHovered = this.hoveredNodeIndex === index; + const inactiveIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair); + const isSelected = SelectionManager.IsSelected(childPair.layout); + + + + + const childScreenToLocal = () => { + const dref = this.docRefs.get(childPair.layout); + const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv); + return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0); + }; + + return ( + <div + key={childPair.layout[Id]} + className={`card-item${isSelected ? '-active' : ''}`} + style={{ + width: this.panelWidth(), + height: this.panelHeight(childPair.layout)(), + transform: ` + rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) + translateY(${isHovered ? this.translateHover(inactiveIndex) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px) + translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px) + scale(${isSelected ? 1.25 : 1}) + `, + }} + onMouseEnter={() => this.setHoveredNodeIndex(index)}> + {displayDoc(childPair, childScreenToLocal)} + </div> + ); + }); + } + + @computed get content() { + // const currentIndex = NumCast(this.layoutDoc._carousel_index); + const amCards = this.inactiveDocs().length; + console.log(amCards + "lol") + // const sortedDocs = this.sortedDocsType.docs; // Retrieve sorted documents + + // const myInactives = + const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => { + return ( + <DocumentView + {...this._props} + ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))} + Document={childPair.layout} + TemplateDataDocument={childPair.data} + // onClickScript={this.toggleIcon} + //suppressSetHeight={true} + NativeWidth={returnZero} + NativeHeight={returnZero} + layout_fitWidth={returnFalse} + onDoubleClickScript={this.onChildDoubleClick} + renderDepth={this._props.renderDepth + 1} + LayoutTemplate={this._props.childLayoutTemplate} + LayoutTemplateString={this._props.childLayoutString} + // focus={this.focus} + ScreenToLocalTransform={screenToLocalTransform} //makes sure the box wrapper thing is in the right spot + isContentActive={this.isChildContentActive} + isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight(childPair.layout)} + /> + ); + }; + + return this.childLayoutPairs.map((childPair, index) => { + const isSelected = this.isSelected(index); + const isHovered = this.hoveredNodeIndex === index; + const isOverflow = amCards > 8 && index > amCards / 2; + // const inactiveIndex = this.sortedDocsType.docs.indexOf(childPair); + const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout); + // const yOffset = this.verticalOffset(index); + + const childScreenToLocal = () => { + const dref = this.docRefs.get(childPair.layout); + const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv); + // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off + return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0); + }; + + return ( + <div + key={childPair.layout[Id]} + className={`card-item${isSelected ? '-active' : ''}`} + style={{ + width: this.panelWidth(), + height: this.panelHeight(childPair.layout)(), + transform: ` + rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) + translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px) + translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px) + scale(${isSelected ? 1.25 : 1}) + + `, //scale has to be applied last or selected offset gets messed up + }} + // onClick={() => this.setSelectedNodeIndex(index)} + + onMouseEnter={() => this.setHoveredNodeIndex(index)}> + {/* {this.lol(childPair.data, index)} */} + + {displayDoc(childPair, childScreenToLocal)} + </div> + ); + }); + } + + @computed get translateWrapperX() { + if (this.inactiveDocs().length != this.childLayoutPairs.length) { + return this.panelWidth() / 2; + } + + return 0; + } + + @computed get renderCardsSort(){ + if (BoolCast(this._props.Document.card_sort_type) == true){ + return this.contentSorted + } + + else{ + return this.content + } + } + + + + render() { + return ( + <div + className="collectionCardView-outer" + ref={this.createDashEventsTarget} + style={{ + background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor), + color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color), + }}> + <div + className="card-wrapper" + style={{ + transform: ` scale(${1 / this.fitContentScale}) translateX(${this.translateWrapperX}px)`, + transformOrigin: 'top left', + height: `${100 * this.fitContentScale}%`, + }} + onMouseLeave={() => this.setHoveredNodeIndex(-1)}> + {this.renderCardsSort} + </div> + + {/* <Card className={`custom-modal-position step-${step}`}> + <Card.Header> + {header} + <Button variant="close" className= "tut-close"onClick={store.toggleTutorial} style={{ position: 'absolute', top: 0, right: 0 }} /> + </Card.Header> + <Card.Body>{body} + <Button className= 'next-step' variant="primary" onClick={store.nextStep}>Next Step!</Button> + </Card.Body> + + + </Card> */} + + {/* {this.focusContent} */} + </div> + ); + } +} diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 4f25f69ef..81d9f4eea 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -319,6 +319,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu return this._freeform_commands; case CollectionViewType.Carousel3D: return this._freeform_commands; + case CollectionViewType.Card: + return this._freeform_commands; + + } } private _commandRef = React.createRef<HTMLInputElement>(); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 18eb4dd1f..b7805bf3f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -33,6 +33,8 @@ import { CollectionLinearView } from './collectionLinear'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; +import { CollectionCardView } from './CollectionCardDeckView'; + export interface CollectionViewProps extends React.PropsWithChildren<FieldViewProps> { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) @@ -134,6 +136,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />; case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />; case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />; + case CollectionViewType.Card: return <CollectionCardView key="collview" {...props} />; + + } }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index c83c26509..becad63f6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -125,6 +125,40 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } +// export function computeCardDeckLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +// console.log('hi'); +// const docMap = new Map<string, PoolData>(); +// const spreadWidth = Math.min(panelDim[0], childPairs.length * 50); // Total width of the spread +// const startX = -(spreadWidth / 2); // Starting X position +// const fanAngle = 5; // Angle in degrees for fanning out cards +// const baseZIndex = 1000; // Base Z-index to ensure cards are stacked in order + +// childPairs.forEach(({ layout, data }, i) => { +// const aspect = NumCast(layout._height) / NumCast(layout._width); +// const docSize = Math.min(400, NumCast(layout._width)) * NumCast(pivotDoc._starburstDocScale, 1); +// const posX = startX + (spreadWidth / childPairs.length) * i; +// const posY = 0; // Adjust if you want to change the vertical alignment +// const rotation = (i - (childPairs.length / 2)) * fanAngle; // Calculate rotation for fanning effect + +// docMap.set(layout[Id], { +// x: posX, +// y: posY, +// width: docSize, +// height: docSize * aspect, +// zIndex: baseZIndex + i, +// rotation: rotation, +// pair: { layout, data }, +// replica: '', +// color: 'white', +// backgroundColor: 'white', +// transition: 'all 0.3s', +// }); +// }); + +// const divider = { type: 'div', color: 'transparent', x: -panelDim[0] / 2, y: -panelDim[1] / 2, width: 15, height: 15, payload: undefined }; +// return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); +// } + export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const docMap = new Map<string, PoolData>(); const fieldKey = 'data'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 079a5d977..2d05b5490 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -49,7 +49,9 @@ import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI'; -import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; +import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, + // computeCardDeckLayout + } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; @@ -1383,6 +1385,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection case computeTimelineLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; case computePivotLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) }; + // case computeCardDeckLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeCardDeckLayout) }; + } return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 6eca91e9d..98684ae98 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -385,6 +385,18 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps this.hideMarquee(); }); + @undoBatch + spreadCards = action((e: KeyboardEvent | React.PointerEvent | undefined) => { + const selected = this.marqueeSelect(false); + SelectionManager.DeselectAll(); + selected.forEach(d => this._props.removeDocument?.(d)); + const newCollection = DocUtils.spreadCards(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!; + this._props.addDocument?.(newCollection); + this._props.selectDocuments([newCollection]); + MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); + }); + /** * This triggers the TabDocView.PinDoc method which is the universal method * used to pin documents to the currently active presentation trail. @@ -510,6 +522,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps @action marqueeCommand = (e: KeyboardEvent) => { + if (this._commandExecuted || (e as any).propagationIsStopped) { return; } @@ -520,7 +533,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps this.delete(e, e.key === 'h'); e.stopPropagation(); } - if ('ctsSpg'.indexOf(e.key) !== -1) { + if ('ctsSpga'.indexOf(e.key) !== -1) { + this._commandExecuted = true; e.stopPropagation(); e.preventDefault(); @@ -528,7 +542,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps if (e.key === 'g') this.collection(e, true); if (e.key === 'c' || e.key === 't') this.collection(e); if (e.key === 's' || e.key === 'S') this.summary(e); - if (e.key === 'p') this.pileup(e); + if (e.key === 'p') this.pileup(e) + // if (e.key === 'a') this.spreadCards(e); + this.cleanupInteractions(false); } if (e.key === 'r' || e.key === ' ') { diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 2a5732708..ffb1d751d 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -12,7 +12,7 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; -import { ActiveFillColor, ActiveInkColor, ActiveInkHideTextLabels, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkHideTextLabels, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke'; +import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; // import { InkTranscription } from '../InkTranscription'; import { DocData } from '../../../fields/DocSymbols'; @@ -21,7 +21,6 @@ import { DocumentView } from '../nodes/DocumentView'; import { VideoBox } from '../nodes/VideoBox'; import { WebBox } from '../nodes/WebBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { ImageBox } from '../nodes/ImageBox'; ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views.length <= 0; @@ -59,9 +58,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b obj[fieldKey] = color; CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj); } else { - const dataKey = Doc.LayoutFieldKey(dv.Document); - const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', ''); - dv.dataDoc[fieldKey + alternate] = color; + dv.Document[DocData][fieldKey] = color; } }); } else { @@ -90,8 +87,8 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole }); } else { Doc.SharingDoc().headingColor = undefined; - Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color; - Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title'); + Doc.GetProto(Doc.SharingDoc()).headingColor = color; + Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date'); } }); @@ -105,10 +102,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); -ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { +ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color', checkResult?: boolean, persist?: boolean) { const selected = SelectionManager.Docs.lastElement(); // prettier-ignore - const map: Map<'center' |'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ + const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ ['grid', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false), setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, @@ -134,6 +131,23 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false), setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters, }], + ['flashcards', { + checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false), + setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards, + }], + ['time', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_time = !doc.card_sort_time, + }], + ['docType', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_type = !doc.card_sort_type, + }], + ['color', { + checkResult: (doc:Doc) => StrCast(doc?.cardSort), + setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_color = !doc.card_sort_color, + }], + ]); if (checkResult) { @@ -144,6 +158,39 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' setTimeout(() => batch.end(), 100); }); +// ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) { +// // const editorView = RichTextMenu.Instance?.TextView?.EditorView; +// const selected = SelectionManager.Docs.lastElement(); +// // prettier-ignore +// const map: Map<'time' | 'docType' | 'color', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ +// ['time', { +// checkResult: (doc:Doc) => StrCast(doc?.cardSort), +// setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "time", +// }], +// ['docType', { +// checkResult: (doc:Doc) => StrCast(doc?.cardSort), +// setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "type", +// }], +// ['color', { +// checkResult: (doc:Doc) => StrCast(doc?.cardSort), +// setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "color", +// }], +// // ['custom', { +// // checkResult: () => RichTextMenu.Instance.textAlign, +// // setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value), +// // }] +// // , +// ]); + +// if (checkResult) { +// return map.get(attr)?.checkResult(selected); +// } + +// console.log('hey') +// SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv)); +// console.log('success') +// }); + ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: any, checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; const selected = SelectionManager.Docs.lastElement(); @@ -151,26 +198,26 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([ ['font', { checkResult: () => RichTextMenu.Instance?.fontFamily, - setDoc: () => value && RichTextMenu.Instance?.setFontFamily(value), + setDoc: () => value && RichTextMenu.Instance.setFontFamily(value), }], ['highlight', { - checkResult: () => RichTextMenu.Instance?.fontHighlight, - setDoc: () => value && RichTextMenu.Instance?.setHighlight(value), + checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight, + setDoc: () => value && RichTextMenu.Instance.setHighlight(value), }], ['fontColor', { checkResult: () => RichTextMenu.Instance?.fontColor, - setDoc: () => value && RichTextMenu.Instance?.setColor(value), + setDoc: () => value && RichTextMenu.Instance.setColor(value), }], ['alignment', { - checkResult: () => RichTextMenu.Instance?.textAlign, - setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value), + checkResult: () => RichTextMenu.Instance.textAlign, + setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value), }], ['fontSize', { checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), setDoc: () => { if (typeof value === 'number') value = value.toString(); if (value && Number(value).toString() === value) value += 'px'; - RichTextMenu.Instance?.setFontSize(value); + RichTextMenu.Instance.setFontSize(value); }, }], ]); @@ -181,46 +228,48 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh map.get(attr)?.setDoc?.(); }); + + type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; -type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }]; +type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }]; ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { const textView = RichTextMenu.Instance?.TextView; const editorView = textView?.EditorView; // prettier-ignore const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) => - [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false: - (RichTextMenu.Instance?.textAlign === where)): + [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance.textVcenter: + (RichTextMenu.Instance.textAlign === where)): where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered): (Doc.UserDoc().textAlign ===where) ? true:false), - toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch): - RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)): + toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance.vcenterToggle(editorView, editorView.dispatch): + RichTextMenu.Instance.align(editorView, editorView.dispatch, where)): where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered: (Doc.UserDoc().textAlign = where))}]); // prettier-ignore // prettier-ignore const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => - [ list, { checkResult: () => (editorView ? RichTextMenu.Instance?.listStyle === list:false), - toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]); + [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false), + toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]); // prettier-ignore const attrs:attrfuncs[] = [ ['dictation', { checkResult: () => textView?._recordingDictation ? true:false, toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }], ['elide', { checkResult: () => false, toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}], - ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false), + ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false), toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], - ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false), - toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], - ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false), - toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], - ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false), - toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] + ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], + ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], + ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] const map = new Map(attrs.concat(alignments).concat(listings)); if (checkResult) { return map.get(charStyle)?.checkResult(); } - undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)(); + undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)(); }); export function checkInksToGroup() { @@ -339,20 +388,15 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); // toggle: Set overlay status of selected document -ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) { +ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) { const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc(); // prettier-ignore - const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ + const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ ['inkMask', { checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())), setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask), setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), }], - ['labels', { - checkResult: () => ((selected?._stroke_showLabel ? BoolCast(selected[DocData].stroke_showLabel) : ActiveInkHideTextLabels())), - setInk: (doc: Doc) => (doc[DocData].stroke_showLabel = !doc.stroke_showLabel), - setMode: () => selected?.type !== DocumentType.INK && SetActiveInkHideTextLabels(!ActiveInkHideTextLabels()), - }], ['fillColor', { checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"), setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)), @@ -407,15 +451,6 @@ ScriptingGlobals.add(function videoSnapshot() { selected?.Snapshot(); }); -ScriptingGlobals.add(function imageSetPixelSize() { - const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox; - selected?.setNativeSize(); -}); -ScriptingGlobals.add(function imageRotate90() { - const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox; - selected?.rotate(); -}); - /** Schema * toggleSchemaPreview **/ @@ -448,10 +483,10 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) { */ ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) { SelectionManager.Docs.map(doc => (doc._text_fontFamily = key)); - const editorView = RichTextMenu.Instance?.TextView?.EditorView; + const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { - return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily); + return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); } - if (editorView) RichTextMenu.Instance?.setFontFamily(key); + if (editorView) RichTextMenu.Instance.setFontFamily(key); else Doc.UserDoc().fontFamily = key; }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 43010b2ed..f856d9637 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -972,11 +972,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: this.Document._layout_autoHeight ? 'lock' : 'unlock', }); - !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); - const help = cm.findByDescription('Help...'); - const helpItems = help && 'subitems' in help ? help.subitems : []; - helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> }); - !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' }); + optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> }); + !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'sliders' }); this._downX = this._downY = Number.NaN; }; |