diff options
Diffstat (limited to 'src')
26 files changed, 147 insertions, 115 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 00f9877c3..1d7497cf8 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -28,13 +28,13 @@ import * as rp from 'request-promise'; export namespace DocServer { let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {}; - export function PRINT_CACHE() { + export function UPDATE_SERVER_CACHE(print: boolean = false) { const strings: string[] = []; Array.from(Object.keys(_cache)).forEach(key => { const doc = _cache[key]; if (doc instanceof Doc) strings.push(StrCast(doc.author) + " " + StrCast(doc.title) + " " + StrCast(Doc.GetT(doc, "title", "string", true))); }); - strings.sort().forEach((str, i) => console.log(i.toString() + " " + str)); + print && strings.sort().forEach((str, i) => console.log(i.toString() + " " + str)); rp.post(Utils.prepend("/setCacheDocumentIds"), { body: { cacheDocumentIds: Array.from(Object.keys(_cache)).join(";"), @@ -348,7 +348,6 @@ export namespace DocServer { } if (requestedIds.length) { - // 2) synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string) // fields for the given ids. This returns a promise, which, when resolved, indicates that all the JSON serialized versions of // the fields have been returned from the server diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 159771145..d7c9af1a3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -236,6 +236,8 @@ class EmptyBox { export namespace Docs { + export let newAccount: boolean = false; + export namespace Prototypes { type LayoutSource = { LayoutString: (key: string) => string }; @@ -392,7 +394,7 @@ export namespace Docs { // non-guid string ids for each document prototype const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix); // fetch the actual prototype documents from the server - const actualProtos = await DocServer.GetRefFields(prototypeIds); + const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds); // update this object to include any default values: DocumentOptions for all prototypes prototypeIds.map(id => { @@ -895,8 +897,8 @@ export namespace Docs { export namespace DocUtils { export function Excluded(d: Doc, docFilters: string[]) { const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields - for (let i = 0; i < docFilters.length; i++) { - const fields = docFilters[i].split(":"); + docFilters.forEach(filter => { + const fields = filter.split(":"); const key = fields[0]; const value = fields[1]; const modifiers = fields[2]; @@ -904,7 +906,7 @@ export namespace DocUtils { filterFacets[key] = {}; } filterFacets[key][value] = modifiers; - } + }); if (d.z) return false; for (const facetKey of Object.keys(filterFacets)) { @@ -921,8 +923,8 @@ export namespace DocUtils { const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields - for (let i = 0; i < docFilters.length; i++) { - const fields = docFilters[i].split(":"); + docFilters.forEach(filter => { + const fields = filter.split(":"); const key = fields[0]; const value = fields[1]; const modifiers = fields[2]; @@ -930,7 +932,7 @@ export namespace DocUtils { filterFacets[key] = {}; } filterFacets[key][value] = modifiers; - } + }); const filteredDocs = docFilters.length ? childDocs.filter(d => { if (d.z) return true; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index dcbeba8cd..f43b6df44 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -878,7 +878,7 @@ export class CurrentUserUtils { // Sharing sidebar is where shared documents are contained static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.myLinkDatabase === undefined) { - let linkDocs = await DocServer.GetRefField(linkDatabaseId); + let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); if (!linkDocs) { linkDocs = new Doc(linkDatabaseId, true); (linkDocs as Doc).author = Doc.CurrentUserEmail; @@ -888,7 +888,7 @@ export class CurrentUserUtils { doc.myLinkDatabase = new PrefetchProxy(linkDocs); } if (doc.mySharedDocs === undefined) { - let sharedDocs = await DocServer.GetRefField(sharingDocumentId + "outer"); + let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); if (!sharedDocs) { sharedDocs = Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, @@ -1024,6 +1024,7 @@ export class CurrentUserUtils { // Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg); // } // }); + setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); return doc; } @@ -1047,8 +1048,12 @@ export class CurrentUserUtils { await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids); if (userDocumentId !== "guest") { - return DocServer.GetRefField(userDocumentId).then(async field => - this.updateUserDocument(Doc.SetUserDoc(field instanceof Doc ? field : new Doc(userDocumentId, true)), sharingDocumentId, linkDatabaseId)); + return DocServer.GetRefField(userDocumentId).then(async field => { + Docs.newAccount = !(field instanceof Doc); + await Docs.Prototypes.initialize(); + const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; + return this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); + }); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } @@ -1108,7 +1113,7 @@ export class CurrentUserUtils { const response = await fetch(upload, { method: "POST", body: formData }); const json = await response.json(); if (json !== "error") { - const doc = await DocServer.GetRefField(json); + const doc = Docs.newAccount ? undefined : await DocServer.GetRefField(json); if (doc instanceof Doc) { setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added. diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index a408e1df6..dc911ea75 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -190,7 +190,7 @@ export class DocumentManager { highlight(); } else { // otherwise try to get a view of the context of the target const targetDocContextView = getFirstDocView(targetDocContext); - targetDocContext._scrollY = targetDocContext._scrollPY = NumCast(targetDocContext._scrollTop, 0); // this will force PDFs to activate and load their annotations / allow scrolling + targetDocContext._scrollY = targetDocContext._scrollPreviewY = NumCast(targetDocContext._scrollTop, 0); // this will force PDFs to activate and load their annotations / allow scrolling if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. targetDocContext._viewTransition = "transform 500ms"; targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 914253e3c..2b13d2a44 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -140,9 +140,9 @@ export class SharingManager extends React.Component<{}> { }); return Promise.all(evaluating).then(() => { runInAction(() => { - for (let i = 0; i < sharingDocs.length; i++) { - if (!this.users.find(user => user.user.email === sharingDocs[i].user.email)) { - this.users.push(sharingDocs[i]); + for (const sharer of sharingDocs) { + if (!this.users.find(user => user.user.email === sharer.user.email)) { + this.users.push(sharer); } } }); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 9dddb4c44..076502042 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -189,7 +189,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin to presentation"}</div></>}> <div className="documentButtonBar-linker" style={{ color: "white" }} - onClick={e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, false))}> + onClick={undoBatch(e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, false)))}> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> </div></Tooltip>; } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 89292a445..fb360ee26 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -56,7 +56,7 @@ export class KeyManager { public handle = action(async (e: KeyboardEvent) => { if (e.key?.toLowerCase() === "shift" && e.ctrlKey && e.altKey) KeyManager.Instance.ShiftPressed = true; - if (!Doc.UserDoc().noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.PRINT_CACHE(); + if (!Doc.UserDoc().noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true); const keyname = e.key && e.key.toLowerCase(); this.handleGreedy(keyname); diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 3889e2d28..c256d2ebb 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -13,10 +13,11 @@ AssignAllExtensions(); (async () => { window.location.search.includes("safe") && CollectionView.SetSafeMode(true); const info = await CurrentUserUtils.loadCurrentUser(); - await Docs.Prototypes.initialize(); if (info.id !== "__guest__") { // a guest will not have an id registered await CurrentUserUtils.loadUserDocument(info.id); + } else { + await Docs.Prototypes.initialize(); } document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index faa341ed9..63a55e168 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -400,7 +400,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp if (targetDoc) { TabDocView.PinDoc(targetDoc, false); const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; - if (targetDoc.type === DocumentType.PDF) { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB) { const scroll = targetDoc._scrollTop; activeDoc.presPinView = true; activeDoc.presPinViewScroll = scroll; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 3607b97d0..fc4ca3100 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -292,25 +292,14 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, if (super.onInternalDrop(e, de)) { const newDocs = de.complete.docDragData.droppedDocuments; const docs = this.childDocList; + DragManager.docsBeingDragged = []; if (docs) { newDocs.map((doc, i) => { - if (i === 0) { - if (doc.presentationTargetDoc) doc.dragging = false; //glr: so it only applies to items in presentation - DragManager.docsBeingDragged = []; - if (targInd === -1) targInd = docs.length; - else targInd = docs.indexOf(this.filteredChildren[targInd]); - const srcInd = docs.indexOf(doc); - docs.splice(srcInd, 1); - docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc); - } else if (i < (newDocs.length / 2)) { //glr: for some reason dragged documents are duplicated - if (doc.presentationTargetDoc) doc.dragging = false; - DragManager.docsBeingDragged = []; - if (targInd === -1) targInd = docs.length; - else targInd = docs.indexOf(newDocs[0]) + 1; - const srcInd = docs.indexOf(doc); - docs.splice(srcInd, 1); - docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc); - } + targInd = targInd === -1 ? docs.length : targInd; + const srcInd = docs.indexOf(doc); + if (targInd !== -1) targInd = i === 0 ? docs.indexOf(this.filteredChildren[targInd]) : docs.indexOf(newDocs[0]) + 1; + docs.splice(srcInd, 1); + docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc); }); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8b9e84bd6..b32a3bd52 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -908,7 +908,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P scrollTo = Math.max(0, NumCast(doc.y) - 50); } if (curScroll !== scrollTo || this.props.Document._viewTransition) { - this.props.Document._scrollPY = this.props.Document._scrollY = scrollTo; + this.props.Document._scrollPreviewY = this.props.Document._scrollY = scrollTo; delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0; !dontCenter && this.props.focus(this.props.Document); afterFocus && setTimeout(afterFocus, delay); @@ -922,6 +922,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } else { const layoutdoc = Doc.Layout(doc); + const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition }; willZoom && this.setScaleToZoom(layoutdoc, scale); const newPanX = (NumCast(doc.x) + doc[WidthSym]() / 2) - (this.isAnnotationOverlay ? (NumCast(this.props.Document._nativeWidth)) / 2 / this.zoomScaling() : 0); @@ -930,7 +931,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); - const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition }; if (DocListCast(this.dataDoc[this.props.annotationsKey || this.props.fieldKey]).includes(doc)) { // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... if (!doc.z) this.setPan(newPanX, newPanY, doc.focusSpeed || doc.focusSpeed === 0 ? `transform ${doc.focusSpeed}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 369b53aa0..e980322d5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -819,14 +819,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: this.toggleRaiseWhenDragged, icon: "expand-arrows-alt" }); !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" }); + onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); + onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" }); + if (!this.Document.annotationOn) { const options = cm.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; !this.props.treeViewDoc && this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform && optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); - onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); - onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "concierge-bell" }); onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" }); onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" }); !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" }); @@ -838,8 +839,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" }); onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" }); onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" }); - !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" }); } + !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" }); } const funcs: ContextMenuProps[] = []; diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 0d02a4388..6f01e5916 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -224,8 +224,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []); - for (let i = 0; i < docFilters.length; i++) { - const fields = docFilters[i].split(":"); // split into key:value:modifiers + for (const filter of docFilters) { + const fields = filter.split(":"); // split into key:value:modifiers if (fields[0] === facetHeader && fields[1] === facetValue) { return fields[2]; } diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 8f9cb5b0e..867be9735 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -64,7 +64,7 @@ export class LinkDocPreview extends React.Component<Props> { this._toolTipText = ""; LinkDocPreview.TargetDoc = this._targetDoc = target; if (anchor !== this._targetDoc && anchor && this._targetDoc) { - this._targetDoc._scrollPY = NumCast(anchor?.y); + this._targetDoc._scrollPreviewY = NumCast(anchor?.y); } }); } diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 7c09bc703..e5f0099a1 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -415,7 +415,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - if (duration <= 100) { duration = 2500; } + if (duration < 100) { duration = 2500; } if (NumCast(targetDoc.lastFrame) > 0) { for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) { await timer(duration / NumCast(targetDoc.lastFrame)); @@ -612,13 +612,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> */ @action sortArray = (): Doc[] => { - const sort: Doc[] = this._selectedArray; - this.childDocs.forEach((doc, i) => { - if (this._selectedArray.includes(doc)) { - sort.push(doc); - } - }); - return sort; + return this.childDocs.filter(doc => this._selectedArray.includes(doc)); } /** @@ -952,11 +946,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> </div> <div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}> - <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "left" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={() => targetDoc.presEffectDirection = 'left'}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "right" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={() => targetDoc.presEffectDirection = 'right'}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === "top" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={() => targetDoc.presEffectDirection = 'top'}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === "bottom" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={() => targetDoc.presEffectDirection = 'bottom'}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection ? "solid 2px black" : "solid 2px #5a9edd", borderRadius: "100%", cursor: "pointer" }} onClick={() => targetDoc.presEffectDirection = false}></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "left" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = 'left')}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "right" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = 'right')}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === "top" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = 'top')}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === "bottom" ? "#5a9edd" : "black", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = 'bottom')}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection ? "solid 2px black" : "solid 2px #5a9edd", borderRadius: "100%", cursor: "pointer" }} onClick={undoBatch(() => targetDoc.presEffectDirection = false)}></div></Tooltip> </div> </div> <div className="ribbon-final-box"> diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 421aac69f..66dc3cdcc 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -14,7 +14,7 @@ import { listSpec, makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { WebField } from "../../../fields/URLField"; import { TraceMobx, GetEffectiveAcl } from "../../../fields/util"; -import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue, OmitKeys } from "../../../Utils"; +import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue, OmitKeys, smoothScroll } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; @@ -87,8 +87,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc._scrollLeft); } this._scrollReactionDisposer?.(); - this._scrollReactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }), - ({ x, y }) => this.updateScroll(x, y), + this._scrollReactionDisposer = reaction(() => ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }), + ({ scrollY, scrollX }) => { + const delay = this._outerRef.current ? 0 : 250; // wait for mainCont and try again to scroll + const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); + const duration = durationStr ? Number(durationStr[1]) : 1000; + if (scrollY !== undefined) { + setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollY || 0)), delay); + setTimeout(() => { this.layoutDoc._scrollTop = scrollY; this.layoutDoc._scrollY = undefined; }, duration + delay); + } + if (scrollX !== undefined) { + setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollX || 0)), delay); + setTimeout(() => { this.layoutDoc._scrollLeft = scrollX; this.layoutDoc._scrollX = undefined; }, duration + delay); + } + }, { fireImmediately: true } ); }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c3946dd57..903bbaaa3 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -23,7 +23,7 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types"; import { TraceMobx, GetEffectiveAcl } from '../../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys } from '../../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys, smoothScroll } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -961,7 +961,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp { fireImmediately: true } ); this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop), - pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } + pos => this._scrollRef.current?.scrollTo({ top: pos }), { fireImmediately: true } + ); + this._disposers.scrollY = reaction(() => Cast(this.layoutDoc._scrollY, "number", null), + scrollY => { + if (scrollY !== undefined) { + const delay = this._scrollRef.current ? 0 : 250; // wait for mainCont and try again to scroll + const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); + const duration = durationStr ? Number(durationStr[1]) : 1000; + setTimeout(() => this._scrollRef.current && smoothScroll(duration, this._scrollRef.current, Math.abs(scrollY || 0)), delay); + setTimeout(() => { this.Document._scrollTop = scrollY; this.Document._scrollY = undefined; }, duration + delay); + } + }, { fireImmediately: true } ); setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight))); diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 0919b2b14..32038d1ee 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -260,7 +260,7 @@ export class FormattedTextBoxComment { const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; if (anchor !== target && anchor && target) { - target._scrollPY = NumCast(anchor?.y); + target._scrollPreviewY = NumCast(anchor?.y); } if (target?.author) { FormattedTextBoxComment.showCommentbox("", view, nbef); diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index a071abd21..20ea7bfe4 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -100,7 +100,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { group.isPushpin = !group.isPushpin; }); - isPushpin = () => BoolCast(Cast(this.props.document.group, Doc, null).isPushpin); + isPushpin = () => BoolCast(Cast(this.props.document.group, Doc, null)?.isPushpin); @action onPointerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index d8be3defd..a8a6e8c33 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -10,7 +10,7 @@ import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; import { createSchema, makeInterface } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast } from "../../../fields/Types"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { PdfField } from "../../../fields/URLField"; import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, emptyPath, intersectRect, returnZero, smoothScroll, Utils, OmitKeys } from "../../../Utils"; @@ -181,22 +181,24 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { const delay = this._mainCont.current ? 0 : 250; // wait for mainCont and try again to scroll - setTimeout(() => this._mainCont.current && smoothScroll(1000, this._mainCont.current, Math.abs(scrollY || 0)), delay); - setTimeout(() => { this.Document._scrollTop = scrollY; this.Document._scrollY = undefined; }, 1000 + delay); + const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); + const duration = durationStr ? Number(durationStr[1]) : 1000; + setTimeout(() => this._mainCont.current && smoothScroll(duration, this._mainCont.current, Math.abs(scrollY || 0)), delay); + setTimeout(() => { this.Document._scrollTop = scrollY; this.Document._scrollY = undefined; }, duration + delay); } } }, { fireImmediately: true } ); - this._disposers.scrollPY = reaction( - () => Cast(this.Document._scrollPY, "number", null), + this._disposers.scrollPreviewY = reaction( + () => Cast(this.Document._scrollPreviewY, "number", null), (scrollY) => { if (scrollY !== undefined) { (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); if (this.props.renderDepth === -1 && scrollY >= 0) { if (!this._mainCont.current) setTimeout(() => this._mainCont.current && smoothScroll(1000, this._mainCont.current, scrollY || 0)); else smoothScroll(1000, this._mainCont.current, scrollY || 0); - this.Document._scrollPY = undefined; + this.Document._scrollPreviewY = undefined; } } }, @@ -353,6 +355,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu mainAnnoDocProto.y = Math.max(minY, 0); mainAnnoDocProto.x = Math.max(maxX, 0); mainAnnoDocProto.type = DocumentType.PDFANNO; + mainAnnoDocProto.text = this._selectionText; mainAnnoDocProto.annotations = new List<Doc>(annoDocs); } mainAnnoDocProto.title = "Annotation on " + this.Document.title; @@ -402,7 +405,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu if (!LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { this.pageDelay && clearTimeout(this.pageDelay); this.pageDelay = setTimeout(() => { - this.Document._scrollY === undefined && (this.layoutDoc._scrollTop = this._mainCont.current!.scrollTop); + this.Document._scrollY === undefined && this._mainCont.current && (this.layoutDoc._scrollTop = this._mainCont.current.scrollTop); this.pageDelay = undefined; //this._pdfViewer && (this.Document._curPage = this._pdfViewer.currentPageNumber); }, 1000); @@ -543,8 +546,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu if (rect) { const scaleY = this._mainCont.current.offsetHeight / boundingRect.height; const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; - if (rect.width !== this._mainCont.current.clientWidth && - (i === 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) { + if (rect.width !== this._mainCont.current.clientWidth) { const annoBox = document.createElement("div"); annoBox.className = "pdfViewerDash-annotationBox"; // transforms the positions from screen onto the pdf div diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 41a8f899c..41a1b5a93 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, runInAction, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DataSym, DocListCast } from "../../../fields/Doc"; import { documentSchema } from '../../../fields/documentSchemas'; @@ -8,13 +8,11 @@ import { createSchema, makeInterface, listSpec } from '../../../fields/Schema'; import { Cast, NumCast, BoolCast, ScriptCast, StrCast } from "../../../fields/Types"; import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero, numberRange, setupMoveUpEvents } from "../../../Utils"; import { Transform } from "../../util/Transform"; -import { CollectionViewType } from '../collections/CollectionView'; import { ViewBoxBaseComponent } from '../DocComponent'; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./PresElementBox.scss"; import React = require("react"); -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { PresBox, PresMovement } from "../nodes/PresBox"; import { DocumentType } from "../../documents/DocumentTypes"; import { Tooltip } from "@material-ui/core"; @@ -47,6 +45,8 @@ const PresDocument = makeInterface(presSchema, documentSchema); export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDocument>(PresDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } _heightDisposer: IReactionDisposer | undefined; + + @observable _dragging = false; // these fields are conditionally computed fields on the layout document that take this document as a parameter @computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements) @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up @@ -161,9 +161,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc e.preventDefault(); } + @action stopDrag = (e: PointerEvent) => { - const activeItem = this.rootDoc; - activeItem.dragging = false; + this._dragging = false; e.stopPropagation(); e.preventDefault(); } @@ -174,7 +174,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc const dragItem: HTMLElement[] = []; PresBox.Instance._dragArray.map(ele => { const doc = ele; - doc.className = "presItem-slide" + doc.className = "presItem-slide"; dragItem.push(doc); }); const addAudioTag = (dropDoc: any) => { @@ -201,7 +201,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc }; if (activeItem) { DragManager.StartDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined, finishDrag); - activeItem.dragging = true; + runInAction(() => this._dragging = true); return true; } return false; @@ -214,11 +214,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc onPointerMove = (e: PointerEvent) => { const slide = this._itemRef.current!; - if (slide && DragManager.docsBeingDragged.length > 1) { + if (slide && DragManager.docsBeingDragged.length > 0) { const rect = slide.getBoundingClientRect(); - let y = e.clientY - rect.top; //y position within the element. - let height = slide.clientHeight; - let halfLine = height / 2; + const y = e.clientY - rect.top; //y position within the element. + const height = slide.clientHeight; + const halfLine = height / 2; if (y <= halfLine) { slide.style.borderTop = "solid 2px #5B9FDD"; slide.style.borderBottom = "0px"; @@ -285,7 +285,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc @computed get mainItem() { const isSelected: boolean = PresBox.Instance._selectedArray.includes(this.rootDoc); - const isDragging: boolean = BoolCast(this.rootDoc.dragging); const toolbarWidth: number = PresBox.Instance.toolbarWidth; const showMore: boolean = PresBox.Instance.toolbarWidth >= 300; const targetDoc: Doc = Cast(this.rootDoc.presentationTargetDoc, Doc, null); @@ -293,7 +292,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc return ( <div className={`presItem-container`} key={this.props.Document[Id] + this.indexInPres} ref={this._itemRef} - style={{ backgroundColor: isSelected ? "#AEDDF8" : "rgba(0,0,0,0)", opacity: isDragging ? 0.3 : 1 }} + style={{ backgroundColor: isSelected ? "#AEDDF8" : "rgba(0,0,0,0)", opacity: this._dragging ? 0.3 : 1 }} onClick={e => { e.stopPropagation(); e.preventDefault(); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index c559d4eb7..3872cbb18 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -122,8 +122,8 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc const filters: string[] = []; - for (let i = 0; i < initialfilters.length; i++) { - const fields = initialfilters[i].split(":"); + for (const initFilter of initialfilters) { + const fields = initFilter.split(":"); if (fields[2] !== undefined) { filters.push(fields[0]); filters.push(fields[1]); @@ -506,7 +506,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc Logoff </div> </div> - <div className="searchBox-lozenge" onClick={() => DocServer.PRINT_CACHE()}> + <div className="searchBox-lozenge" onClick={() => DocServer.UPDATE_SERVER_CACHE()}> {`UI project`} </div> <div className="searchBox-lozenge-dashboard" > diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index c8d28b4a2..1a062fa3b 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -399,7 +399,7 @@ export namespace Doc { // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype'). export function GetProto(doc: Doc): Doc { if (doc instanceof Promise) { - console.log("GetProto: warning: got Promise insead of Doc"); + // console.log("GetProto: warning: got Promise insead of Doc"); } const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc)); return proto === doc ? proto : Doc.GetProto(proto); @@ -500,7 +500,6 @@ export namespace Doc { alias.title = ComputedField.MakeFunction(`renameAlias(this, ${Doc.GetProto(doc).aliasNumber = NumCast(Doc.GetProto(doc).aliasNumber) + 1})`); } alias.author = Doc.CurrentUserEmail; - alias[AclSym] = doc[AclSym]; Doc.AddDocToList(doc[DataSym], "aliases", alias); diff --git a/src/fields/List.ts b/src/fields/List.ts index a0cbebaf5..215dff34b 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -78,7 +78,7 @@ const listHandlers: any = { } const res = list.__fields.splice(start, deleteCount, ...items); this[Update](items.length === 0 && deleteCount ? { op: "$remFromSet", items: removed, length: list.__fields.length } : - items.length && !deleteCount ? { op: "$addToSet", items, length: list.__fields.length } : undefined); + items.length && !deleteCount && start === list.__fields.length ? { op: "$addToSet", items, length: list.__fields.length } : undefined); return res.map(toRealField); }), unshift(...items: any[]) { diff --git a/src/fields/util.ts b/src/fields/util.ts index d48011194..a374c7f54 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -162,7 +162,7 @@ export function GetEffectiveAcl(target: any, user?: string): symbol { } function getPropAcl(target: any, prop: string | symbol | number) { - if (prop === UpdatingFromServer || target[UpdatingFromServer] || prop == AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent + if (prop === UpdatingFromServer || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent if (prop && DocServer.PlaygroundFields?.includes(prop.toString())) return AclEdit; // playground props are always editable return GetEffectiveAcl(target); } @@ -363,47 +363,59 @@ export function deleteProperty(target: any, prop: string | number | symbol) { } export function updateFunction(target: any, prop: any, value: any, receiver: any) { - let current = ObjectField.MakeCopy(value); + let lastValue = ObjectField.MakeCopy(value); return (diff?: any) => { const op = diff?.op === "$addToSet" ? { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } : diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; !op.$set && ((op as any).length = diff.length); - - const oldValue = current; + const prevValue = ObjectField.MakeCopy(lastValue as List<any>); + lastValue = ObjectField.MakeCopy(value); const newValue = ObjectField.MakeCopy(value); - current = newValue; + if (!(value instanceof CursorField) && !(value?.some?.((v: any) => v instanceof CursorField))) { !receiver[UpdatingFromServer] && UndoManager.AddEvent( diff?.op === "$addToSet" ? { redo: () => { - receiver[prop].push(...diff.items); + receiver[prop].push(...diff.items.map((item: any) => item.value())); + lastValue = ObjectField.MakeCopy(receiver[prop]); }, undo: action(() => { - const curList = receiver[prop]; - //while (curList[ForwardUpates]) curList = curList[ForwardUpates]; diff.items.forEach((doc: any) => { - const ind = curList.indexOf(doc.value()); - ind !== -1 && curList.splice(ind, 1); + const ind = receiver[prop].indexOf(doc.value()); + ind !== -1 && receiver[prop].splice(ind, 1); }); + lastValue = ObjectField.MakeCopy(receiver[prop]); }) } : diff?.op === "$remFromSet" ? { redo: action(() => { - const curList = receiver[prop]; diff.items.forEach((doc: any) => { - const ind = curList.indexOf(doc.value()); - ind !== -1 && curList.splice(ind, 1); + const ind = receiver[prop].indexOf(doc.value()); + ind !== -1 && receiver[prop].splice(ind, 1); }); + lastValue = ObjectField.MakeCopy(receiver[prop]); }), - undo: () => receiver[prop].push(...diff.items) + undo: () => { + diff.items.map((item: any) => { + const ind = (prevValue as List<any>).indexOf(diff.items[0].value()); + ind !== -1 && receiver[prop].indexOf(diff.items[0].value()) === -1 && receiver[prop].splice(ind, 0, item); + }); + lastValue = ObjectField.MakeCopy(receiver[prop]); + } } : { - redo: () => receiver[prop] = newValue, - undo: () => receiver[prop] = oldValue + redo: () => { + receiver[prop] = ObjectField.MakeCopy(newValue as List<any>); + lastValue = ObjectField.MakeCopy(receiver[prop]); + }, + undo: () => { + receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>); + lastValue = ObjectField.MakeCopy(receiver[prop]); + } }); } target[Update](op); diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 1e02b9e58..490760441 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -207,9 +207,12 @@ export namespace WebSocket { } } + function GetRefFieldLocal([id, callback]: [string, (result?: Transferable) => void]) { + return Database.Instance.getDocument(id, callback); + } function GetRefField([id, callback]: [string, (result?: Transferable) => void]) { process.stdout.write(`.`); - Database.Instance.getDocument(id, callback); + GetRefFieldLocal([id, callback]); } function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) { @@ -311,8 +314,11 @@ export namespace WebSocket { function UpdateField(socket: Socket, diff: Diff) { - if (diff.diff.$addToSet) return GetRefField([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own - if (diff.diff.$remFromSet) return GetRefField([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own + if (diff.diff.$addToSet) return GetRefFieldLocal([diff.id, (result?: Transferable) => addToListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own + if (diff.diff.$remFromSet) return GetRefFieldLocal([diff.id, (result?: Transferable) => remFromListField(socket, diff, result)]); // would prefer to have Mongo handle list additions direclty, but for now handle it on our own + return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]); + } + function SetField(socket: Socket, diff: Diff, curListItems?: Transferable) { Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false); const docfield = diff.diff.$set || diff.diff.$unset; |