diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/views/LightboxView.tsx | 8 | ||||
| -rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 13 | ||||
| -rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 10 | ||||
| -rw-r--r-- | src/client/views/collections/CollectionView.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/collections/TabDocView.tsx | 6 | ||||
| -rw-r--r-- | src/client/views/collections/TreeView.tsx | 63 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 52 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/button/FontIconBox.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/trails/PresBox.tsx | 125 | ||||
| -rw-r--r-- | src/client/views/nodes/trails/PresElementBox.scss | 78 | ||||
| -rw-r--r-- | src/client/views/nodes/trails/PresElementBox.tsx | 110 | ||||
| -rw-r--r-- | src/fields/InkField.ts | 3 |
15 files changed, 422 insertions, 57 deletions
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 9882693ee..ddfc8b249 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -38,6 +38,7 @@ export class LightboxView extends React.Component<LightboxViewProps> { private static _history: Opt<{ doc: Doc, target?: Doc }[]> = []; @observable private static _future: Opt<Doc[]> = []; private static _docView: Opt<DocumentView>; + private static openInTabFunc: any; static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, scrollTop: Opt<number> }> }[] = []; @action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc) { if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) { @@ -106,7 +107,8 @@ export class LightboxView extends React.Component<LightboxViewProps> { this._docFilters = (f => this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f])(`cookies:${cookie}:provide`); } } - public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc) => { + public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc, openInTabFunc?: any) => { + LightboxView.openInTabFunc = openInTabFunc; SelectionManager.DeselectAll(); return LightboxView.SetLightboxDoc(doc, undefined, [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), @@ -273,7 +275,9 @@ export class LightboxView extends React.Component<LightboxViewProps> { <div className="lightboxView-tabBtn" title={"open in tab"} onClick={e => { e.stopPropagation(); - CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, "onRight"); + // CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, ""); + LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace"); + console.log("lightbox to tab triggered") SelectionManager.DeselectAll(); LightboxView.SetLightboxDoc(undefined); }}> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 55190001b..a74a6fda9 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -250,7 +250,7 @@ export class MainView extends React.Component { })); } const pres = Docs.Create.PresDocument({ title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); - CollectionDockingView.AddSplit(pres, "right"); + CollectionDockingView.AddSplit(pres, "left"); this.userDoc.activePresentation = pres; Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 509da18cb..7e1299a2e 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -452,7 +452,7 @@ export class CollectionDockingView extends CollectionSubView() { alert('cant delete the last stack'); } })); - stack.header?.controlsContainer.find('.lm_popout') //get the close icon + stack.header?.controlsContainer.find('.lm_popout') //get the popout icon .off('click') //unbind the current click handler .click(action(() => { // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 58289a161..7f96217b8 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -59,6 +59,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @observable _paletteOn = false; @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + _ele: HTMLElement | null = null; createColumnDropRef = (ele: HTMLDivElement | null) => { @@ -69,6 +70,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this)); } } + componentWillUnmount() { this.props.unobserveHeight(this._ele); } @@ -237,6 +239,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y); ContextMenu.Instance.displayMenu(x, y, undefined, true); } + + + @computed get innards() { TraceMobx(); const key = this.props.pivotField; @@ -307,7 +312,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC }}> {this.props.renderChildren(this.props.docList)} </div> - {!this.props.chromeHidden && type !== DocumentType.PRES ? + {!this.props.chromeHidden ? <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}> <EditableView @@ -317,7 +322,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC contents={"+ NEW"} toggle={this.toggleVisibility} menuCallback={this.menuCallback} /> - </div> : null} + </div> + : null + } + + </div> } </>; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index c49580046..e809bfbce 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -38,6 +38,10 @@ export type collectionTreeViewProps = { treeViewSkipFields?: string[]; // prevents specific fields from being displayed (see LinkBox) onCheckedClick?: () => ScriptField; onChildClick?: () => ScriptField; + // TODO: [AL] add these fields + AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; + RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; + hierarchyIndex?: number[]; }; export enum TreeViewType { @@ -265,7 +269,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, "boolean", null), this.observeHeight, this.unobserveHeight, - this.childContextMenuItems() + this.childContextMenuItems(), + //TODO: [AL] add these + this.props.AddToMap, + this.props.RemFromMap, + this.props.hierarchyIndex, ); } @computed get titleBar() { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 4f92e305e..0de61c9b2 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -79,6 +79,10 @@ export interface CollectionViewProps extends FieldViewProps { childIgnoreNativeSize?: boolean; childClickScript?: ScriptField; childDoubleClickScript?: ScriptField; + //TODO: [AL] add these fields + AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; + RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; + hierarchyIndex?: number[]; } @observer export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps>() { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index be68e1001..8d84e9a56 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -295,7 +295,11 @@ export class TabDocView extends React.Component<TabDocViewProps> { case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); - case "lightbox": return LightboxView.AddDocTab(doc, location); + // case "lightbox": { + // // TabDocView.PinDoc(doc, { hidePresBox: true }); + // return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); + // } + case "lightbox": return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack); case "inPlace": case "add": diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 70ad23f41..3840afb04 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,6 +1,6 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; @@ -14,7 +14,7 @@ import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { DocumentManager } from '../../util/DocumentManager'; +import { DocumentManager, DocFocusOrOpen } from '../../util/DocumentManager'; import { DragManager, dropActionType } from "../../util/DragManager"; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -64,6 +64,10 @@ export interface TreeViewProps { onChildClick?: () => ScriptField; skipFields?: string[]; firstLevel: boolean; + // TODO: [AL] add these + AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; + RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; + hierarchyIndex?: number[]; } const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("px", "")); }; @@ -109,7 +113,7 @@ export class TreeView extends React.Component<TreeViewProps> { get defaultExpandedView() { return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : this.props.treeView.dashboardMode ? this.fieldKey : - this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : + this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : // for displaying this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields"); } @@ -127,6 +131,16 @@ export class TreeView extends React.Component<TreeViewProps> { @computed get selected() { return SelectionManager.IsSelected(this._docRef); } // SelectionManager.Views().lastElement()?.props.Document === this.props.document; } + @observable _presTimer!: NodeJS.Timeout; + @observable _presKeyEventsActive: boolean = false; + + @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>(); + // the selected item's index + @computed get itemIndex() { return NumCast(this.doc._itemIndex); } + // the item that's active + @computed get activeItem() { return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined; } + @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } + childDocList(field: string) { const layout = Cast(Doc.LayoutField(this.doc), Doc, null); return (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field @@ -198,6 +212,13 @@ export class TreeView extends React.Component<TreeViewProps> { this._treeEle && this.props.unobserveHeight(this._treeEle); document.removeEventListener("pointermove", this.onDragMove, true); document.removeEventListener("pointermove", this.onDragUp, true); + // TODO: [AL] add these + this.props.RemFromMap?.(this.doc, this.props.hierarchyIndex!); + } + + componentDidMount() { + // TODO: [AL] add these + this.props.AddToMap?.(this.doc, this.props.hierarchyIndex!); } onDragUp = (e: PointerEvent) => { @@ -364,7 +385,12 @@ export class TreeView extends React.Component<TreeViewProps> { this.props.dropAction, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged, - this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems()); + this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), + // TODO: [AL] Add these + this.props.AddToMap, + this.props.RemFromMap, + this.props.hierarchyIndex + ); } else { contentElement = <EditableView key="editableView" contents={contents !== undefined ? Field.toString(contents as Field) : "null"} @@ -458,7 +484,11 @@ export class TreeView extends React.Component<TreeViewProps> { StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged, - this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems())} + this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), + // TODO: [AL] add these + this.props.AddToMap, + this.props.RemFromMap, + this.props.hierarchyIndex)} </ul > </>; } else if (this.treeViewExpandedView === "fields") { @@ -574,7 +604,12 @@ export class TreeView extends React.Component<TreeViewProps> { const icons = StrListCast(this.doc.childContextMenuIcons); return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); } - onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); + + // TODO: currently doc focus works, but can't seem to edit title + // onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); + onChildClick = () => { + return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.()) + } onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); @@ -762,6 +797,7 @@ export class TreeView extends React.Component<TreeViewProps> { fitContentsToDoc={returnTrue} hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} + onClick={this.onChildClick} focus={this.refocus} ContentScaling={returnOne} onKey={this.onKeyDown} @@ -828,7 +864,10 @@ export class TreeView extends React.Component<TreeViewProps> { return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles <div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`} ref={this.createTreeDropTarget} - onDrop={this.onTreeDrop}> + onDrop={this.onTreeDrop} + //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document + // onKeyDown={this.onKeyDown} + > <li className="collection-child"> {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader" this.renderEmbeddedDocument(false, returnFalse) : @@ -893,7 +932,11 @@ export class TreeView extends React.Component<TreeViewProps> { dontRegisterView: boolean | undefined, observerHeight: (ref: any) => void, unobserveHeight: (ref: any) => void, - contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[]) + contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[]), + // TODO: [AL] add these + AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[], + RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[], + hierarchyIndex?: number[], ) { const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField); if (viewSpecScript) { @@ -934,6 +977,10 @@ export class TreeView extends React.Component<TreeViewProps> { dataDoc={pair.data} containerCollection={containerCollection} prevSibling={docs[i]} + // TODO: [AL] add these + hierarchyIndex={[...hierarchyIndex!, i + 1]} + AddToMap={AddToMap} + RemFromMap={RemFromMap} treeView={treeView} indentDocument={indent} outdentDocument={outdent} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 29bdae170..0338fcb18 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -52,7 +52,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @observable _lastY: number = 0; @observable _downX: number = 0; @observable _downY: number = 0; - @observable _visible: boolean = false; + @observable _visible: boolean = false; // selection rentangle for marquee selection/free hand lasso is visible @observable _lassoPts: [number, number][] = []; @observable _lassoFreehand: boolean = false; @@ -207,8 +207,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this._downY = this._lastY = e.clientY; if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; - // allow marquee if right click OR alt+left click - if (e.button === 2 || (e.button === 0 && e.altKey)) { + // allow marquee if right click OR alt+left click OR in adding presentation slide & left key drag mode + if (e.button === 2 || (e.button === 0 && e.altKey) || (PresBox.startMarquee && e.button === 0)) { // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) { this.setPreviewCursor(e.clientX, e.clientY, true, false); // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors. @@ -239,6 +239,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this } e.altKey && e.preventDefault(); + if (PresBox.startMarquee) { + e.stopPropagation(); + } } @action @@ -259,7 +262,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque document.removeEventListener("pointerdown", hideMarquee); document.removeEventListener("wheel", hideMarquee); }; - if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) { + if (PresBox.startMarquee) { + this.pinMarqueeView(); + PresBox.startMarquee = false; + } + if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100) && !PresBox.startMarquee) { MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; @@ -380,6 +387,41 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action + pinMarqueeView = () => { + const doc = this.props.Document; + const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; + if (curPres) { + console.log(curPres); + if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; } + const pinDoc = Doc.MakeAlias(doc); + pinDoc.presentationTargetDoc = doc; + pinDoc.presMovement = PresMovement.Zoom; + pinDoc.groupWithUp = false; + pinDoc.context = curPres; + pinDoc.title = doc.title + " - Slide"; + const presArray = PresBox.Instance?.sortArray(); + const size = PresBox.Instance?._selectedArray.size; + const presSelected = presArray && size ? presArray[size - 1] : undefined; + Doc.AddDocToList(curPres, "data", pinDoc, presSelected); + if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; + if (!DocumentManager.Instance.getDocumentView(curPres)) { + CollectionDockingView.AddSplit(curPres, "right"); + } + PresBox.Instance?._selectedArray.clear(); + pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Updates selected array + const index = PresBox.Instance?.childDocs.indexOf(pinDoc); + index && (curPres._itemIndex = index); + const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height); + pinDoc.presPinView = true; + pinDoc.presPinViewX = this.Bounds.left + this.Bounds.width / 2; + pinDoc.presPinViewY = this.Bounds.top + this.Bounds.height / 2; + pinDoc.presPinViewScale = scale; + } + this.hideMarquee(); + } + + @undoBatch + @action pinWithView = (e: KeyboardEvent | React.PointerEvent | undefined) => { const doc = this.props.Document; const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; @@ -642,7 +684,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque return <div className="marqueeView" style={{ overflow: StrCast(this.props.Document._overflow), - cursor: CurrentUserUtils.SelectedTool === InkTool.Pen ? "crosshair" : "pointer" + cursor: CurrentUserUtils.SelectedTool === InkTool.Pen || this._visible || PresBox.startMarquee ? "crosshair" : "pointer" }} onDragOver={e => e.preventDefault()} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9db4cbde3..14ededaf8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -501,7 +501,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click"); } else if (!Doc.IsSystem(this.rootDoc)) { UndoManager.RunInBatch(() => - LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.()) + LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.(), this.props.addDocTab) , "double tap"); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 625f5d14a..d29160458 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -231,6 +231,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const script = ScriptCast(this.rootDoc.script); + if (!script) { return null }; let noviceList: string[] = []; let text: string | undefined; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 077db9077..d85f0b640 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -90,13 +90,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @observable _expandBoolean: boolean = false; private _disposers: { [name: string]: IReactionDisposer } = {}; + + @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView @observable private transitionTools: boolean = false; @observable private newDocumentTools: boolean = false; @observable private progressivizeTools: boolean = false; @observable private openMovementDropdown: boolean = false; @observable private openEffectDropdown: boolean = false; @observable private presentTools: boolean = false; - @computed get childDocs() { return DocListCast(this.rootDoc[this.fieldKey]); } + // @computed get childDocs() { return DocListCast(this.rootDoc[this.fieldKey]); } + // TODO: [AL] add childDocs() and treeMap fields + // @computed get childDocs() { return DocListCast(this.rootDoc.presentationaLinearizedDocuments); } + //_treeViewMap:Map<number[], Doc> + @computed get childDocs() { return DocListCast(this.rootDoc.presentationLinearizedDocuments); } + @observable _treeViewMap: Map<number, Doc> = new Map<number, Doc>(); + @computed get tagDocs() { const tagDocs: Doc[] = []; for (const doc of this.childDocs) { @@ -132,6 +140,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { { field: "string", data: Doc.name, container: Doc.name }); } this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox + //TODO: [AL] change this + this.props.Document.presentationLinearizedDocuments = new List<Doc>(); } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; @@ -212,6 +222,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time + // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions // No more frames in current doc and next slide is defined, therefore move to next slide nextSlide = (activeNext: Doc) => { const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); @@ -375,7 +386,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); }); const openInTab = (doc: Doc, finished?: () => void) => { - collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, ":left"); + collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, ""); this.layoutDoc.presCollection = targetDoc; // this still needs some fixing setTimeout(resetSelection, 500); @@ -387,7 +398,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }; // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { - LightboxView.SetLightboxDoc(targetDoc); + // LightboxView.SetLightboxDoc(targetDoc); + // activeItem: "slide in the sidebar" --> maps to target document (targetDoc) in the canvas + console.log(activeItem); + console.log("openDocument triggered"); + + openInTab(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { LightboxView.SetLightboxDoc(undefined); await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right @@ -735,6 +751,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { //Regular click @action selectElement = async (doc: Doc) => { + console.log("tree docs are:", this._treeViewMap); + console.log("child docs are:", this.childDocs); + console.log("linearized docs:", this.rootDoc.presentationLinearizedDocuments); + console.log("got here", this.childDocs.indexOf(doc)); const context = Cast(doc.context, Doc, null); this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem); if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); @@ -2404,6 +2424,45 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { else this.pauseAutoPres(); } + @action + startMarqueeCreateSlide = () => { + PresBox.startMarquee = true; + } + + AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => { + var indexNum = 0; + for (let i = 0; i < index.length; i++) { + indexNum += (index[i] * (10 ** (-i))); + } + this._treeViewMap.set(indexNum, treeViewDoc); + console.log(String(index), treeViewDoc) + this.props.Document.presentationLinearizedDocuments = new List<Doc>(this.sort(this._treeViewMap)); // this is a flat array of Docs + console.log(this.props.Document.presentationLinearizedDocuments) + return this.childDocs; + } + + RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => { + var indexNum = 0; + for (let i = 0; i < index.length; i++) { + indexNum += (index[i] * (10 ** (-i))); + } + console.log(String(index), treeViewDoc) + this._treeViewMap.delete(indexNum) + this.props.Document.presentationLinearizedDocuments = new List<Doc>(this.sort(this._treeViewMap)); + return this.childDocs; + } + + // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1]) + sort = (treeViewMap: Map<number, Doc>): Doc[] => { + // TODO + const sortedMap = [...treeViewMap.entries()].sort(); + var sortedDocs = []; + for (var kv of sortedMap) { + sortedDocs.push(kv[1]); + } + return sortedDocs; + } + render() { // calling this method for keyEvents this.isPres; @@ -2437,33 +2496,47 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { {this.toolbar} {this.newDocumentToolbarDropdown} <div className="presBox-listCont"> - {mode !== CollectionViewType.Invalid ? - <CollectionView {...this.props} - ContainingCollectionDoc={this.props.Document} - PanelWidth={this.props.PanelWidth} - PanelHeight={this.panelHeight} - childIgnoreNativeSize={true} - moveDocument={returnFalse} - childFitWidth={returnTrue} - childOpacity={returnOne} - childLayoutTemplate={this.childLayoutTemplate} - filterAddDocument={this.addDocumentFilter} - removeDocument={returnFalse} - dontRegisterView={true} - focus={this.selectElement} - ScreenToLocalTransform={this.getTransform} - /> - : (null) + <div className="Slide" style={{ height: `calc(100% - 100px)` }}> + {mode !== CollectionViewType.Invalid ? + <CollectionView {...this.props} + ContainingCollectionDoc={this.props.Document} + PanelWidth={this.props.PanelWidth} + PanelHeight={this.panelHeight} + childIgnoreNativeSize={true} + moveDocument={returnFalse} + childFitWidth={returnTrue} + childOpacity={returnOne} + childLayoutTemplate={this.childLayoutTemplate} + filterAddDocument={this.addDocumentFilter} + removeDocument={returnFalse} + dontRegisterView={true} + focus={this.selectElement} + ScreenToLocalTransform={this.getTransform} + // TODO: [AL] implement AddToMap, RemFromMap functions (outside this) + AddToMap={this.AddToMap} + RemFromMap={this.RemFromMap} + hierarchyIndex={[]} + /> : (null) + } + </div> + + { // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack + <Tooltip title={<div className="dash-tooltip">{"Click on document to pin to presentaiton or make a marquee selection to pin your desired view"}</div>}> + <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}> + + NEW SLIDE + </button> + </Tooltip> } </div> </div>; } } -ScriptingGlobals.add(function lookupPresBoxField(container: Doc, field: string, data: Doc) { - if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data); - if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(container._viewType as any) ? 35 : 31; - if (field === 'presStatus') return container.presStatus; - if (field === '_itemIndex') return container._itemIndex; - if (field === 'presBox') return container; +// this func communicates with PresBoxElement to send information of the doc +ScriptingGlobals.add(function lookupPresBoxField(presBoxDoc: Doc, field: string, presEleDoc: Doc) { + if (field === 'indexInPres') return DocListCast(presBoxDoc.presentationLinearizedDocuments).indexOf(presEleDoc); + if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(presBoxDoc._viewType as any) ? 35 : 31; + if (field === 'presStatus') return presBoxDoc.presStatus; + if (field === '_itemIndex') return presBoxDoc._itemIndex; + if (field === 'presBox') return presBoxDoc; return undefined; });
\ No newline at end of file diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss index a178be910..789a6d9f3 100644 --- a/src/client/views/nodes/trails/PresElementBox.scss +++ b/src/client/views/nodes/trails/PresElementBox.scss @@ -195,6 +195,47 @@ $slide-active: #5B9FDD; box-shadow: 0 0 0px 1.5px $dark-blue; } +.presItem-slide.group { + border-radius: 5px; +} + +.presItem-slide.activeGroup { + border-radius: 5px; + box-shadow: 0 0 0px 1.5px $dark-blue; +} + +.presItem-groupSlideContainer { + position: absolute; + /* grid-row: 3; */ + /* grid-column: 1/8; */ + top: 28; + display: block; + background: #92adb9; + width: 100%; + height: 10px; + border-radius: 0px 0px 5px 5px; + height: calc(100% - 28px); + display: grid; + grid-template-rows: auto auto auto; + grid-template-columns: 100%; + justify-items: left; + align-items: center; +} + +.presItem-groupSlide { + position: relative; + background-color: #d5dce2; + border-radius: 5px; + height: calc(100% - 7px); + width: calc(100% - 20px); + left: 15px; + /* height: 20px; */ + /* width: calc(100% - 19px); */ + display: flex; + grid-template-rows: 16px 10px auto; + grid-template-columns: max-content max-content max-content max-content auto; +} + .presItem-multiDrag { font-family: Roboto; font-weight: 600; @@ -230,6 +271,37 @@ $slide-active: #5B9FDD; box-shadow: 0 0 0px 1.5px $dark-blue; } -// .presItem-slide:hover { -// background: $slide-hover; -// }
\ No newline at end of file +.expandButton { + cursor: pointer; + position: absolute; + border-radius: 100px; + bottom: 0; + left: -18; + z-index: 300; + width: 15px; + height: 15px; + display: flex; + font-size: 12px; + justify-self: center; + align-self: center; + background-color: #92adb9; + color: white; + justify-content: center; + align-items: center; + transition: 0.2s; + margin-right: 3px; +} + +.expandButton:hover { + background-color: rgba(0, 0, 0, 1); + transform: scale(1.2); +} + +.presItem-groupNum { + color: #d5dce2; + position: absolute; + left: -15px; + top: 1; + font-weight: 600; + font-size: 12; +}
\ No newline at end of file diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index ef918d991..bbabcb2de 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -2,9 +2,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, Opt } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, Opt } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; -import { Cast, NumCast, StrCast } from "../../../../fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils"; import { DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; @@ -107,6 +107,39 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { </div>; } + @computed get renderGroupSlides() { + const childDocs = DocListCast(this.targetDoc.data); + const groupSlides = childDocs.map((doc: Doc, ind: number) => + <div className="presItem-groupSlide" onClick={(e) => { + console.log("Clicked on slide with index: ", ind); + e.stopPropagation(); + e.preventDefault(); + PresBox.Instance.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); + this.presExpandDocumentClick(); + }}> + <div className="presItem-groupNum"> + {`${ind + 1}.`} + </div> + {/* style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }} */} + <div className="presItem-name"> + <EditableView + ref={this._titleRef} + editing={undefined} + contents={doc.title} + overflow={'ellipsis'} + GetValue={() => StrCast(doc.title)} + SetValue={(value: string) => { + doc.title = !value.trim().length ? "-untitled-" : value; + return true; + }} + /> + </div> + </div> + ); + return ( + groupSlides + ); + } @computed get duration() { let durationInS: number; if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; } @@ -289,6 +322,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false; const targetDoc: Doc = this.targetDoc; const activeItem: Doc = this.rootDoc; + const isGroup: boolean = BoolCast(targetDoc._isGroup); return ( <div className={`presItem-container`} key={this.props.Document[Id] + this.indexInPres} @@ -303,8 +337,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); }} onDoubleClick={action(e => { - this.toggleProperties(); - PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true); + if (isGroup) { + this.rootDoc.presExpandGroup = !this.rootDoc.presExpandGroup; + } else { + this.toggleProperties(); + PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true); + } })} onPointerOver={this.onPointerOver} onPointerLeave={this.onPointerLeave} @@ -321,7 +359,69 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presItem-number"> {`${this.indexInPres + 1}.`} </div>} - {miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`} + {isGroup ? + <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "activeGroup" : "group"}`} + style={{ + backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), + boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined + }}> + <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}> + <EditableView + ref={this._titleRef} + editing={!isSelected ? false : undefined} + contents={activeItem.title} + overflow={'ellipsis'} + GetValue={() => StrCast(activeItem.title)} + SetValue={this.onSetValue} + /> + </div> + <div className={"presItem-slideButtons"}> + <Tooltip title={<><div className="dash-tooltip">{"Update view"}</div></>}> + <div className="slideButton" + onClick={() => this.updateView(targetDoc, activeItem)} + style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V</div> + </Tooltip> + {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{activeItem.groupWithUp ? "Ungroup" : "Group with up"}</div></>}> + <div className="slideButton" + onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp} + style={{ + zIndex: 1000 - this.indexInPres, + fontWeight: 700, + backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined, + height: activeItem.groupWithUp ? 53 : 18, + transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined + }}> + <div style={{ transform: activeItem.groupWithUp ? "rotate(180deg) translate(0, -17.5px)" : "rotate(0deg)" }}> + <FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} /> + </div> + </div> + </Tooltip>} + <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}</div></>}><div className={"slideButton"} onClick={e => { e.stopPropagation(); this.presExpandDocumentClick(); }}> + <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? "eye-slash" : "eye"} onPointerDown={e => e.stopPropagation()} /> + </div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Remove from presentation"}</div></>}><div + className={"slideButton"} + onClick={this.removeItem}> + <FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} /> + </div></Tooltip> + <div className="group"></div> + </div> + <div className="presItem-groupSlideContainer" style={{ top: 28, height: 'calc(100% - 28px)' }}> + {this.rootDoc.presExpandGroup ? this.renderGroupSlides : (null)} + </div> + <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div> + <div className="expandButton" + onClick={e => { + if (isGroup) { + this.rootDoc.presExpandGroup = !this.rootDoc.presExpandGroup; + } + }} + > + <FontAwesomeIcon icon={"caret-down"} style={{ transform: this.rootDoc.presExpandGroup ? "rotate(180deg)" : "rotate(0deg)" }} /> + </div> + </div> + : (null)} + {miniView || isGroup ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`} style={{ backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 31024e805..1039a7dfc 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -11,7 +11,8 @@ export enum InkTool { Pen = "pen", Highlighter = "highlighter", Eraser = "eraser", - Stamp = "stamp" + Stamp = "stamp", + PresentationPin = 'presentationpin' } |
