From d3383640a8007a15e0deb47715e203508c4fd64a Mon Sep 17 00:00:00 2001 From: aidahosa1 Date: Tue, 19 Mar 2024 16:21:34 -0400 Subject: working on implementing card logic --- src/client/documents/Documents.ts | 16 +- src/client/util/CurrentUserUtils.ts | 2 +- .../views/collections/CollectionCardDeckView.scss | 0 .../views/collections/CollectionCardDeckView.tsx | 281 +++++++++++---------- src/client/views/collections/CollectionMenu.tsx | 4 + .../CollectionFreeFormLayoutEngines.tsx | 61 ++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- 7 files changed, 198 insertions(+), 172 deletions(-) create mode 100644 src/client/views/collections/CollectionCardDeckView.scss (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d12c61fd7..c453fd793 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1219,14 +1219,7 @@ export namespace Docs { ); } - export function CardDeckDocument(documents: Array, options: DocumentOptions, id?: string) { - return InstanceFromProto( - Prototypes.get(DocumentType.COL), - new List(documents), - { backgroundColor: 'transparent', dropAction: dropActionType.move, _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Card }, - id - ); - } + export function LinearDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Linear }, id); @@ -1244,6 +1237,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Carousel3D }); } + export function CardDeckDocument(documents: Array, 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, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaHeaders: new List(schemaHeaders), ...options, _type_collection: CollectionViewType.Schema }); } @@ -1887,6 +1884,7 @@ export namespace DocUtils { } 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); @@ -1897,7 +1895,7 @@ export namespace DocUtils { 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; // Assuming 'd.rotation' sets the rotation, adjust accordingly + d.rotation = angle; d._timecodeToShow = undefined; }); }); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 11f6c82ec..15c3ddad6 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -796,7 +796,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_)'} }, diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index feb9e61cc..e35f7b421 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -1,164 +1,185 @@ -import { action, computed, IReactionDisposer, makeObservable } from 'mobx'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils'; import { Doc, DocListCast } from '../../../fields/Doc'; -import { ScriptField } from '../../../fields/ScriptField'; -import { NumCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; -import { DocUtils } from '../../documents/Documents'; +import { Id } from '../../../fields/FieldSymbols'; +import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DragManager } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; -import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { OpenWhere } from '../nodes/DocumentView'; -import { computePassLayout, computeStarburstLayout, computeCardDeckLayout } from './collectionFreeForm'; -import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; -import './CollectionPileView.scss'; +import { StyleProp } from '../StyleProvider'; +import { DocumentView } from '../nodes/DocumentView'; +import { FocusViewOptions } from '../nodes/FieldView'; +import './CollectionCarousel3DView.scss'; import { CollectionSubView } from './CollectionSubView'; -import { dropActionType } from '../../util/DragManager'; - +const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore @observer export class CollectionCardView extends CollectionSubView() { - _originalChrome: any = ''; - _disposers: { [name: string]: IReactionDisposer } = {}; - + @computed get scrollSpeed() { + return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed + } constructor(props: any) { super(props); makeObservable(this); } - componentDidMount() { - if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeCardDeckLayout.name) { - this.Document._freeform_cardEngine = computePassLayout.name; - } - this._originalChrome = this.layoutDoc._chromeHidden; - this.layoutDoc._chromeHidden = true; - } + private _dropDisposer?: DragManager.DragDropDisposer; + componentWillUnmount() { - this.layoutDoc._chromeHidden = this._originalChrome; - Object.values(this._disposers).forEach(disposer => disposer?.()); + this._dropDisposer?.(); } - layoutEngine = () => StrCast(this.Document._freeform_cardEngine); - - @undoBatch - addCardDoc = (doc: Doc | Doc[]) => { - (doc instanceof Doc ? [doc] : doc).map(d => DocUtils.iconify(d)); - return this._props.addDocument?.(doc) || false; + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + this._dropDisposer?.(); + if (ele) { + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); + } }; - @undoBatch - removeCardDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d)); - const ret = this._props.moveDocument?.(doc, targetCollection, addDoc) || false; - if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.DocumentView?.()._props.removeDocument?.(this.Document); - return ret; + centerScale = Number(CAROUSEL3D_CENTER_SCALE); + panelWidth = () => this._props.PanelWidth() / 3; + panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE); + onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); + isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); + isChildContentActive = () => (this.isContentActive() ? true : false); + childScreenToLocal = () => + this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs. But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1) + .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor. + .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2) + .scale(1 / this.centerScale); + + focus = (anchor: Doc, options: FocusViewOptions) => { + const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]); + if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return; + options.didMove = true; + const target = DocCast(anchor.annotationOn) ?? anchor; + const index = docs.indexOf(target); + index !== -1 && (this.layoutDoc._carousel_index = index); //if index is not -1, then assign index to this.layoutDoc._carousel_index + return undefined; }; - @computed get toggleIcon() { - return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' }); - } - @computed get contentEvents() { - const isCard = this.layoutEngine() === computeCardDeckLayout.name; - return this._props.isContentActive() && isCard ? undefined : 'none'; + + @computed get content() { + const currentIndex = NumCast(this.layoutDoc._carousel_index); + const displayDoc = (childPair: { layout: Doc; data: Doc }) => { + return ( + + ); + }; + + return this.childLayoutPairs.map((childPair, index) => { + return ( +
+ {displayDoc(childPair)} +
+ ); + }); } - // returns the contents of the cardSpread in a CollectionFreeFormView - @computed get contents() { + changeSlide = (direction: number) => { + SelectionManager.DeselectAll(); + this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length; + }; + + onArrowClick = (direction: number) => { + this.changeSlide(direction); + !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = direction === 1 ? 'fwd' : 'back'); // while autoscroll is on, keep the other autoscroll button hidden + !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on + }; + + interval?: number; + startAutoScroll = (direction: number) => { + this.interval = window.setInterval(() => { + this.changeSlide(direction); + }, this.scrollSpeed); + }; + + stopAutoScroll = () => { + window.clearInterval(this.interval); + this.interval = undefined; + this.fadeScrollButton(); + }; + + toggleAutoScroll = (direction: number) => { + this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true; + this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll(); + }; + + fadeScrollButton = () => { + window.setTimeout(() => { + !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked. + }, 1500); + }; + + @computed get buttons() { return ( -
- +
+
this.onArrowClick(-1)} /> +
this.onArrowClick(1)} /> + {/* {this.autoScrollButton} */}
); } - // // toggles the pileup between starburst to compact - // toggleStarburst = action(() => { - // this.layoutDoc._freeform_scale = undefined; - // if (this.layoutEngine() === computeStarburstLayout.name) { - // if (NumCast(this.layoutDoc._width) !== NumCast(this.Document._starburstDiameter, 500)) { - // this.Document._starburstDiameter = NumCast(this.layoutDoc._width); - // } - // const defaultSize = 110; - // this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2; - // this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2; - // this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize); - // this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize); - // DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false); - // this.layoutDoc._freeform_panX = 0; - // this.layoutDoc._freeform_panY = -10; - // this.Document._freeform_pileEngine = computePassLayout.name; - // } else { - // const defaultSize = NumCast(this.Document._starburstDiameter, 400); - // this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2; - // this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2; - // this.layoutDoc._freeform_pileWidth = NumCast(this.layoutDoc._width); - // this.layoutDoc._freeform_pileHeight = NumCast(this.layoutDoc._height); - // this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0; - // this.layoutDoc._width = this.layoutDoc._height = defaultSize; - // this.layoutDoc.background; - // this.Document._freeform_pileEngine = computeStarburstLayout.name; - // } - // }); - - // for dragging documents out of the pileup view - _undoBatch: UndoManager.Batch | undefined; - pointerDown = (e: React.PointerEvent) => { - let dist = 0; - setupMoveUpEvents( - this, - e, - (e: PointerEvent, down: number[], delta: number[]) => { - if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) { - dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]); - if (dist > 100) { - if (!this._undoBatch) { - this._undoBatch = UndoManager.StartBatch('layout pile'); - } - const doc = this.childDocs[0]; - doc.x = e.clientX; - doc.y = e.clientY; - this._props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this._props.removeDocument?.(doc) || false); - dist = 0; - } - } - return false; - }, - () => { - this._undoBatch?.end(); - this._undoBatch = undefined; - }, - emptyFunction, - e.shiftKey && this.layoutEngine() === computePassLayout.name, - this.layoutEngine() === computePassLayout.name && e.shiftKey - ); // this sets _doubleTap - }; + @computed get autoScrollButton() { + const whichButton = this.layoutDoc.showScrollButton; + return ( + <> +
this.toggleAutoScroll(-1)}> + {this.layoutDoc.autoScrollOn ? : } +
+
this.toggleAutoScroll(1)}> + {this.layoutDoc.autoScrollOn ? : } +
+ + ); + } - // onClick for toggling the pileup view - // @undoBatch - // onClick = (e: React.MouseEvent) => { - // if (e.button === 0) { - // SelectionManager.DeselectAll(); - // this.toggleStarburst(); - // e.stopPropagation(); - // } - // }; + @computed get dots() { + return this.childLayoutPairs.map((_child, index) =>
(this.layoutDoc._carousel_index = index)} />); + } + @computed get translateX() { + const index = NumCast(this.layoutDoc._carousel_index); + return this.panelWidth() * (1 - index); + } render() { return ( -
- {this.contents} +
+
+ {this.content} +
+ {this.buttons} +
{this.dots}
); } + + } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 8729ef549..9d135eb3b 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(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index e972f44f1..b3ae382e3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -126,39 +126,40 @@ export function computeStarburstLayout(poolData: Map, pivotDoc return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } -export function computeCardDeckLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { - const docMap = new Map(); - 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 +// export function computeCardDeckLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { +// console.log('hi'); +// const docMap = new Map(); +// 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 +// 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, // Increment Z-index for each card to stack them correctly - rotation: rotation, // Optional: Add this if you want to rotate elements for a fanned effect - pair: { layout, data }, - replica: '', - color: 'white', - backgroundColor: 'white', - transition: 'all 0.3s', - }); - }); +// 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', +// }); +// }); - // This is a placeholder for the divider object and may need to be adjusted based on actual usage - 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]); -} +// // This is a placeholder for the divider object and may need to be adjusted based on actual usage +// 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, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const docMap = new Map(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f9fe306fa..10b849dab 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, computeCardDeckLayout } 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,7 +1385,7 @@ export class CollectionFreeFormView extends CollectionSubView