From 80a8e0a7e48ee84cb121b80d72d485bc10e9269b Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Thu, 1 Oct 2020 19:32:21 +0800 Subject: redesigned presItems --- src/client/views/collections/CollectionStackingView.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index daaee67dc..d41e0528d 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -294,14 +294,17 @@ export class CollectionStackingView extends CollectionSubView { - console.log(doc.title); if (i === 0) { + doc.dragging = false; + 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 + doc.dragging = false; + DragManager.docsBeingDragged = []; if (targInd === -1) targInd = docs.length; else targInd = docs.indexOf(newDocs[0]) + 1; const srcInd = docs.indexOf(doc); -- cgit v1.2.3-70-g09d2 From 3d266cb4636aad2e6fe39595b113b0dd52f4f329 Mon Sep 17 00:00:00 2001 From: geireann <60007097+geireann@users.noreply.github.com> Date: Sun, 4 Oct 2020 20:20:08 +0800 Subject: stepping through PDF fixes --- src/client/views/collections/TabDocView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 30 +++---- src/client/views/nodes/PresBox.tsx | 27 +----- .../views/presentationview/PresElementBox.scss | 99 +--------------------- 4 files changed, 24 insertions(+), 134 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 8c1a003b9..70eb5b895 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -123,7 +123,7 @@ export class TabDocView extends React.Component { @undoBatch @action public static PinDoc(doc: Doc, unpin = false, audioRange?: boolean) { - if (unpin) console.log('remove unpin'); + if (unpin) console.log('TODO: Remove UNPIN from this location'); //add this new doc to props.Document const curPres = CurrentUserUtils.ActivePresentation; if (curPres) { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index d6f8ce19c..5a8ce4e14 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -65,7 +65,7 @@ export class CollectionFreeFormDocumentView extends DocComponent (i <= timecode && w !== undefined) || p === undefined ? w : p, undefined as any as number), x: Cast(doc["x-indexed"], listSpec("number"), [NumCast(doc.x)]).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number), y: Cast(doc["y-indexed"], listSpec("number"), [NumCast(doc.y)]).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number), - scroll: Cast(doc["scroll-indexed"], listSpec("number"), [NumCast(doc._scrollY, 0)]).reduce((p, s, i) => (i <= timecode && s !== undefined) || p === undefined ? s : p, undefined as any as number), + scroll: Cast(doc["scroll-indexed"], listSpec("number"), [NumCast(doc._scrollTop, 0)]).reduce((p, s, i) => (i <= timecode && s !== undefined) || p === undefined ? s : p, undefined as any as number), opacity: Cast(doc["opacity-indexed"], listSpec("number"), [NumCast(doc.opacity, 1)]).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number), }); } @@ -97,24 +97,24 @@ export class CollectionFreeFormDocumentView extends DocComponent doc.dataTransition = "inherit", 1010); - } + // public static updateScrollframe(doc: Doc, time: number) { + // console.log('update scroll frame'); + // const timecode = Math.round(time); + // const scrollIndexed = Cast(doc['scroll-indexed'], listSpec("number"), null); + // scrollIndexed?.length <= timecode + 1 && scrollIndexed.push(undefined as any as number); + // setTimeout(() => doc.dataTransition = "inherit", 1010); + // } - public static setupScroll(doc: Doc, timecode: number) { - const scrollList = new List(); - scrollList[timecode] = NumCast(doc._scrollY); - doc["scroll-indexed"] = scrollList; - doc.activeFrame = ComputedField.MakeFunction("self._currentFrame"); - doc._scrollY = ComputedField.MakeInterpolated("scroll", "activeFrame"); - } + // public static setupScroll(doc: Doc, timecode: number) { + // const scrollList = new List(); + // scrollList[timecode] = NumCast(doc._scrollTop); + // doc["scroll-indexed"] = scrollList; + // doc.activeFrame = ComputedField.MakeFunction("self._currentFrame"); + // doc._scrollTop = ComputedField.MakeInterpolated("scroll", "activeFrame"); + // } public static updateKeyframe(docs: Doc[], time: number, targetDoc?: Doc) { diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 739f564a5..2af00586b 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -153,7 +153,6 @@ export class PresBox extends ViewBoxBaseComponent setTimeout(() => targetDoc._viewTransition = undefined, 1010); // targetDoc._currentFrame = curFrame + 1; this.nextKeyframe(targetDoc, activeItem); - // if (targetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(targetDoc, currentFrame); if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); else targetDoc.editing = true; // if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(targetDoc); @@ -1252,22 +1251,13 @@ export class PresBox extends ViewBoxBaseComponent const currentFrame = Cast(tagDoc._currentFrame, "number", null); if (currentFrame === undefined) { tagDoc._currentFrame = 0; - CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); + // CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } - CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); + // if (tagDoc.editScrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); - // if (curDoc.zoomProgressivize) { - // const resize = document.getElementById('resizable'); - // if (resize) { - // resize.style.width = this.checkList(tagDoc, curDoc["viewfinder-width-indexed"]) + 'px'; - // resize.style.height = this.checkList(tagDoc, curDoc["viewfinder-height-indexed"]) + 'px'; - // resize.style.top = this.checkList(tagDoc, curDoc["viewfinder-top-indexed"]) + 'px'; - // resize.style.left = this.checkList(tagDoc, curDoc["viewfinder-left-indexed"]) + 'px'; - // } - // } } @undoBatch @@ -1281,15 +1271,6 @@ export class PresBox extends ViewBoxBaseComponent } CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); - // if (actItem.zoomProgressivize) { - // const resize = document.getElementById('resizable'); - // if (resize) { - // resize.style.width = this.checkList(tagDoc, actItem["viewfinder-width-indexed"]) + 'px'; - // resize.style.height = this.checkList(tagDoc, actItem["viewfinder-height-indexed"]) + 'px'; - // resize.style.top = this.checkList(tagDoc, actItem["viewfinder-top-indexed"]) + 'px'; - // resize.style.left = this.checkList(tagDoc, actItem["viewfinder-left-indexed"]) + 'px'; - // } - // } } /** @@ -1474,7 +1455,7 @@ export class PresBox extends ViewBoxBaseComponent activeItem.scrollProgressivize = !activeItem.scrollProgressivize; const targetDoc: Doc = this.targetDoc; targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize; - CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); + // CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); if (targetDoc.editScrollProgressivize) { targetDoc.editScrollProgressivize = false; targetDoc._currentFrame = 0; @@ -1803,7 +1784,7 @@ export class PresBox extends ViewBoxBaseComponent {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} -
+
{mode !== CollectionViewType.Invalid ? Date: Thu, 8 Oct 2020 21:57:50 -0400 Subject: added direct typing of notes into stacking columns in text box sidebar. --- src/client/views/EditableView.tsx | 7 +++++++ src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 6 ++++++ 2 files changed, 13 insertions(+) (limited to 'src/client/views/collections') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index d35271ffd..8b1b12365 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -47,6 +47,7 @@ export interface EditableProps { onClick?: (e: React.MouseEvent) => boolean; isEditingCallback?: (isEditing: boolean) => void; menuCallback?: (x: number, y: number) => void; + textCallback?: (char: string) => boolean; showMenuOnLoad?: boolean; HeadingObject?: SchemaHeaderField | undefined; toggle?: () => void; @@ -119,6 +120,12 @@ export class EditableView extends React.Component { case ":": this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); break; + case "Shift": case "Alt": case "Meta": case "Control": break; + default: + if (this.props.textCallback?.(e.key)) { + this._editing = false; + this.props.isEditingCallback?.(false,); + } } } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 1bc989e83..74ea824cd 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -130,6 +130,11 @@ export class CollectionStackingViewFieldColumn extends React.Component { + return this.addDocument(char, false); + } + @action addDocument = (value: string, shiftDown?: boolean) => { if (!value) return false; @@ -300,6 +305,7 @@ export class CollectionStackingViewFieldColumn extends React.Component "", SetValue: this.addDocument, + textCallback: this.textCallback, contents: "+ NEW", HeadingObject: this.props.headingObject, toggle: this.toggleVisibility, -- cgit v1.2.3-70-g09d2 From c150d0afa46c2c45e413a5269d26bc46b64ba765 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 8 Oct 2020 23:21:04 -0400 Subject: fixed dragging bottom of some pdfs not to grow wildly. stopped writing renderContentBounds to annotation views. fixed texts sidebar notes to not add extra chars on creation. --- src/client/views/DocumentDecorations.tsx | 2 +- .../views/collections/CollectionStackingViewFieldColumn.tsx | 8 ++++---- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 066cfd95a..66f0dd99d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -450,7 +450,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> height = !height || isNaN(height) ? 20 : height; const scale = docView.props.ScreenToLocalTransform().Scale * docView.props.ContentScaling(); if (nwidth && nheight) { - if (nwidth / nheight !== width / height) { + if (nwidth / nheight !== width / height && !dragBottom) { height = nheight / nwidth * width; } if (e.ctrlKey || (!dragBottom || !docView.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 74ea824cd..b7562c45e 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -132,12 +132,12 @@ export class CollectionStackingViewFieldColumn extends React.Component { - return this.addDocument(char, false); + return this.addDocument("", false, true); } @action - addDocument = (value: string, shiftDown?: boolean) => { - if (!value) return false; + addDocument = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { + if (!value && !forceEmptyNote) return false; const key = StrCast(this.props.parent.props.Document._pivotField); const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, title: value, _autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); @@ -145,7 +145,7 @@ export class CollectionStackingViewFieldColumn extends React.Component this.Document._renderContentBounds = new List([this.contentBounds.x, this.contentBounds.y, this.contentBounds.r, this.contentBounds.b]), 0); + !this.fitToContent && !this.props.annotationsKey && this._layoutElements?.length && setTimeout(() => this.Document._renderContentBounds = new List([this.contentBounds.x, this.contentBounds.y, this.contentBounds.r, this.contentBounds.b]), 0); return
Date: Sat, 10 Oct 2020 12:10:48 -0400 Subject: fixes to issues with updating interactively whe groups change. fixes for multiple rapid modifications to list field where values would be overwrriten when sever sendBack stale information. fixes to list.splice() where deleting nothing but adding something didn't result in an addToSet. --- src/client/util/CurrentUserUtils.ts | 14 +++++-- src/client/util/GroupManager.tsx | 5 +++ src/client/util/SharingManager.tsx | 28 ++++++++----- src/client/util/SnappingManager.ts | 8 ++++ .../views/collections/CollectionStackingView.tsx | 1 - src/fields/List.ts | 5 ++- src/fields/util.ts | 46 ++++++---------------- src/server/websocket.ts | 6 ++- 8 files changed, 61 insertions(+), 52 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 580c6040e..694982fea 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -8,9 +8,9 @@ import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, NumCast, PromiseValue, StrCast, DateCast } from "../../fields/Types"; import { nullAudio } from "../../fields/URLField"; -import { SharingPermissions, UserGroups } from "../../fields/util"; +import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; @@ -32,6 +32,7 @@ import { Scripting } from "./Scripting"; import { SearchUtil } from "./SearchUtil"; import { SelectionManager } from "./SelectionManager"; import { UndoManager } from "./UndoManager"; +import { SnappingManager } from "./SnappingManager"; export let resolvedPorts: { server: number, socket: number }; @@ -957,8 +958,13 @@ export class CurrentUserUtils { static async updateUserDocument(doc: Doc, sharingDocumentId: string) { if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); - await DocListCastAsync((doc.globalGroupDatabase as Doc).data); - UserGroups.Current; + const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); + reaction(() => DateCast((doc.globalGroupDatabase as Doc).lastModified), + async () => { + const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); + const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; + SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); + }, { fireImmediately: true }); doc.system = true; doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 63e2a8024..cc1d45a58 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -13,6 +13,7 @@ import "./GroupManager.scss"; import { GroupMemberView } from "./GroupMemberView"; import { SharingManager, User } from "./SharingManager"; import { listSpec } from "../../fields/Schema"; +import { DateField } from "../../fields/DateField"; /** * Interface for options for the react-select component @@ -144,6 +145,7 @@ export class GroupManager extends React.Component<{}> { */ addGroup(groupDoc: Doc): boolean { if (this.GroupManagerDoc) { + this.GroupManagerDoc.lastModified = new DateField; Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc); return true; } @@ -158,6 +160,7 @@ export class GroupManager extends React.Component<{}> { deleteGroup(group: Doc): boolean { if (group) { if (this.GroupManagerDoc && this.hasEditAccess(group)) { + this.GroupManagerDoc.lastModified = new DateField; Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); SharingManager.Instance.removeGroup(group); const members = JSON.parse(StrCast(group.members)); @@ -184,6 +187,7 @@ export class GroupManager extends React.Component<{}> { const memberList = JSON.parse(StrCast(groupDoc.members)); !memberList.includes(email) && memberList.push(email); groupDoc.members = JSON.stringify(memberList); + this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField); SharingManager.Instance.shareWithAddedMember(groupDoc, email); } } @@ -200,6 +204,7 @@ export class GroupManager extends React.Component<{}> { if (index !== -1) { const user = memberList.splice(index, 1)[0]; groupDoc.members = JSON.stringify(memberList); + this.GroupManagerDoc && (this.GroupManagerDoc.lastModified = new DateField); SharingManager.Instance.removeMember(groupDoc, email); } } diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index ee397ab4f..16bcd46c8 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -22,6 +22,7 @@ import "./SharingManager.scss"; import { SelectionManager } from "./SelectionManager"; import { intersection } from "lodash"; import { SearchBox } from "../views/search/SearchBox"; +import { listSpec } from "../../fields/Schema"; export interface User { email: string; @@ -172,8 +173,8 @@ export class SharingManager extends React.Component<{}> { group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(doc) : group.docsShared = new List([doc]); users.forEach(({ user, sharingDoc }) => { - if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(sharingDoc[storage])) === -1 && Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(sharingDoc[storage])) !== -1 && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists + if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added + else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists }); } }); @@ -189,9 +190,13 @@ export class SharingManager extends React.Component<{}> { const self = this; if (group.docsShared) { if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false)); - else DocListCastAsync(group.docsShared).then(dl => dl?.forEach(doc => { - Doc.AddDocToList(user.sharingDoc, storage, doc); - })); + else { + DocListCastAsync(user.sharingDoc[storage]).then(userdocs => + DocListCastAsync(group.docsShared).then(dl => { + const filtered = dl?.filter(doc => !userdocs?.includes(doc)); + filtered && userdocs?.push(...filtered); + })); + } } } @@ -222,9 +227,12 @@ export class SharingManager extends React.Component<{}> { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; if (group.docsShared) { - DocListCastAsync(group.docsShared).then(dl => dl?.forEach(doc => { - Doc.RemoveDocFromList(user.sharingDoc, storage, doc); - })); + DocListCastAsync(user.sharingDoc[storage]).then(userdocs => + DocListCastAsync(group.docsShared).then(dl => { + const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || []; + userdocs?.splice(0, userdocs.length, ...remaining); + }) + ); } } @@ -261,8 +269,8 @@ export class SharingManager extends React.Component<{}> { doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc); distributeAcls(acl, permission as SharingPermissions, doc); - if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(sharingDoc[storage])) === -1 && Doc.AddDocToList(sharingDoc, storage, doc); - else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(sharingDoc[storage])) !== -1 && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); + if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); + else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); }); } diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index fc07e8ab4..d067dff6c 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -14,6 +14,9 @@ export namespace SnappingManager { this.horizSnapLines = horizLines; this.vertSnapLines = vertLines; } + + @observable cachedGroups: string[] = []; + @action setCachedGroups(groups: string[]) { this.cachedGroups = groups; } } const manager = new Manager(); @@ -25,5 +28,10 @@ export namespace SnappingManager { export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); } export function GetIsDragging() { return manager.IsDragging; } + + /// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts + // need to investigate further what caused the mobx update problems and move to a better location. + export function SetCachedGroups(groups: string[]) { manager.setCachedGroups(groups); } + export function GetCachedGroups() { return manager.cachedGroups; } } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index daaee67dc..72ce041e1 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -294,7 +294,6 @@ export class CollectionStackingView extends CollectionSubView { - console.log(doc.title); if (i === 0) { if (targInd === -1) targInd = docs.length; else targInd = docs.indexOf(this.filteredChildren[targInd]); diff --git a/src/fields/List.ts b/src/fields/List.ts index ceb538b2d..a0cbebaf5 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -43,7 +43,7 @@ const listHandlers: any = { } } const res = list.__fields.push(...items); - this[Update]({ op: "$addToSet", items }); + this[Update]({ op: "$addToSet", items, length: length + items.length }); return res; }), reverse() { @@ -77,7 +77,8 @@ const listHandlers: any = { } } const res = list.__fields.splice(start, deleteCount, ...items); - this[Update](items.length === 0 && deleteCount ? { op: "$remFromSet", items: removed } : undefined); + 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); return res.map(toRealField); }), unshift(...items: any[]) { diff --git a/src/fields/util.ts b/src/fields/util.ts index 791b98b83..fd409a54e 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,5 +1,5 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto, AclUnset } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto, AclUnset, DocListCastAsync } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; @@ -8,10 +8,13 @@ import { action, trace, observable, reaction, computed } from "mobx"; import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate, ToString, ToScriptString } from "./FieldSymbols"; import { DocServer } from "../client/DocServer"; import { ComputedField } from "./ScriptField"; -import { ScriptCast, StrCast } from "./Types"; +import { ScriptCast, StrCast, DateCast, Cast, NumCast } from "./Types"; import { returnZero } from "../Utils"; import CursorField from "./CursorField"; import { List } from "./List"; +import { listSpec } from "./Schema"; +import { DateField } from "./DateField"; +import { SnappingManager } from "../client/util/SnappingManager"; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); @@ -22,32 +25,6 @@ export function TraceMobx() { tracing && trace(); } -// the list of groups that the current user is a member of -export class UserGroups { - static computing = false; - static cachedGroups: string[] = []; - static globalGroupDoc: Doc | undefined; - static get Current() { - if (!Doc.UserDoc() || UserGroups.computing) return UserGroups.cachedGroups; - UserGroups.computing = true; - if (!UserGroups.globalGroupDoc) UserGroups.globalGroupDoc = Doc.UserDoc().globalGroupDatabase as Doc; - if (UserGroups.globalGroupDoc) { - const dbgroups = DocListCast(UserGroups.globalGroupDoc.data); - if (dbgroups.length !== UserGroups.cachedGroups.length - 1) { - UserGroups.cachedGroups = [ - "Public", - ...dbgroups?. - filter(group => group instanceof Doc). - map(group => group as Doc). - filter(group => JSON.parse(StrCast(group.members))?.includes(Doc.CurrentUserEmail)). - map(group => StrCast(group.title)) - ]; - } - } - UserGroups.computing = false; - return UserGroups.cachedGroups; - } -} export interface GetterResult { value: FieldResult; @@ -190,7 +167,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, // if the current user is the author of the document / the current user is a member of the admin group const userChecked = user || Doc.CurrentUserEmail; if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; - if (UserGroups.Current?.includes("Admin")) return AclAdmin; + if (SnappingManager.GetCachedGroups().includes("Admin")) return AclAdmin; if (target[AclSym] && Object.keys(target[AclSym]).length) { @@ -211,7 +188,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, // there are issues with storing fields with . in the name, so they are replaced with _ during creation // as a result we need to restore them again during this comparison. const entity = denormalizeEmail(key.substring(4)); // an individual or a group - if (UserGroups.Current?.includes(entity) || userChecked === entity) { + if (SnappingManager.GetCachedGroups().includes(entity) || userChecked === entity) { if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { effectiveAcl = value as symbol; if (effectiveAcl === AclAdmin) return effectiveAcl; @@ -388,9 +365,12 @@ 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); return (diff?: any) => { - const op = diff?.op === "$addToSet" ? { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List(diff.items)) } } : - diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List(diff.items)) } } - : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; + const op = + diff?.op === "$addToSet" ? { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List(diff.items)) } } : + diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List(diff.items)) } } + : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; + !op['$set'] && ((op as any).length = diff.length); + const oldValue = current; const newValue = ObjectField.MakeCopy(value); current = newValue; diff --git a/src/server/websocket.ts b/src/server/websocket.ts index c9b5d1cbf..03795d254 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -277,7 +277,8 @@ export namespace WebSocket { const newListItems = diff.diff.$set[updatefield].fields; const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields || []; diff.diff.$set[updatefield].fields = [...curList, ...newListItems.filter((newItem: any) => !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : false))]; - const sendBack = true;//curList.length !== prelen; + const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; + delete diff.diff.length; Database.Instance.update(diff.id, diff.diff, () => { if (sendBack) { @@ -295,7 +296,8 @@ export namespace WebSocket { const remListItems = diff.diff.$set[updatefield].fields; const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields || []; diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : false)); - const sendBack = true;//curList.length + remListItems.length !== prelen; + const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; + delete diff.diff.length; Database.Instance.update(diff.id, diff.diff, () => { if (sendBack) { -- cgit v1.2.3-70-g09d2 From 7314eace7c9df5d283b9c02cf08cc25857b88dd4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 10 Oct 2020 13:58:48 -0400 Subject: fixed showing ink tools when text is unselected. --- src/client/views/collections/CollectionMenu.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 429764154..bf6067978 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -528,7 +528,9 @@ export class CollectionFreeFormViewChrome extends React.Component Date: Sat, 10 Oct 2020 17:56:34 -0400 Subject: fixed scrolling down to annotations that have no height (but really do -- eg pdf selections) to at least show 50 pixels. --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 796765a8d..d48961849 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -896,7 +896,7 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(doc.y)) { scrollTo = NumCast(doc.y); } -- cgit v1.2.3-70-g09d2 From 858fdbf31c9ddbf00eae692cd43e70455a19e1b5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 10 Oct 2020 20:49:26 -0400 Subject: fixed docFilters to use up one string field, not 3. makes adding/removing to lists incrementally work with new server code. --- src/client/documents/Documents.ts | 14 ++++++++++---- src/client/util/LinkManager.ts | 2 +- .../views/collections/CollectionSchemaHeaders.tsx | 14 ++++++++------ src/client/views/nodes/FilterBox.tsx | 16 ++++++++-------- src/client/views/search/SearchBox.tsx | 20 +++++++++++--------- src/fields/Doc.ts | 13 ++++++------- src/server/websocket.ts | 4 ++-- 7 files changed, 46 insertions(+), 37 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7d78bd76a..159771145 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -895,8 +895,11 @@ 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 += 3) { - const [key, value, modifiers] = docFilters.slice(i, i + 3); + for (let i = 0; i < docFilters.length; i++) { + const fields = docFilters[i].split(":"); + const key = fields[0]; + const value = fields[1]; + const modifiers = fields[2]; if (!filterFacets[key]) { filterFacets[key] = {}; } @@ -918,8 +921,11 @@ 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 += 3) { - const [key, value, modifiers] = docFilters.slice(i, i + 3); + for (let i = 0; i < docFilters.length; i++) { + const fields = docFilters[i].split(":"); + const key = fields[0]; + const value = fields[1]; + const modifiers = fields[2]; if (!filterFacets[key]) { filterFacets[key] = {}; } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index e74ce87f8..1ba6cff6d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -36,7 +36,7 @@ export class LinkManager { public getAllLinks(): Doc[] { const lset = new Set(DocListCast(Doc.LinkDBDoc().data)); SharingManager.Instance.users.forEach(user => { - DocListCast((user.linkDatabase as Doc)?.data).map(doc => { + DocListCast(user.linkDatabase?.data).map(doc => { lset.add(doc); }); }); diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index dbf7488ec..b408fd680 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -261,7 +261,7 @@ export class KeysDropdown extends React.Component { componentDidMount() { document.addEventListener("pointerdown", this.detectClick); const filters = Cast(this.props.Document._docFilters, listSpec("string")); - if (filters?.includes(this._key)) { + if (filters?.some(filter => filter.split(":")[0] === this._key)) { runInAction(() => this.closeResultsVisibility = "contents"); } } @@ -396,19 +396,21 @@ export class KeysDropdown extends React.Component { }); const filters = Cast(this.props.Document._docFilters, listSpec("string")); - if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) { + if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) { this.props.col.setColor("rgb(241, 239, 235)"); this.closeResultsVisibility = "none"; } - for (let i = 0; i < (filters?.length ?? 0) - 1; i += 3) { - if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i + 1]) === false) { + for (let i = 0; i < (filters?.length ?? 0) - 1; i++) { + if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i].split(":")[1]) === false) { keyOptions.push(filters![i + 1]); } } const options = keyOptions.map(key => { let bool = false; if (filters !== undefined) { - bool = filters.includes(key) && filters[filters.indexOf(key) + 1] === "check"; + const ind = filters.findIndex(filter => filter.split(":")[0] === key); + const fields = ind === -1 ? undefined : filters[ind].split(":"); + bool = fields ? fields[1] === "check" : false; } return
{ updateFilter() { const filters = Cast(this.props.Document._docFilters, listSpec("string")); - if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) { + if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) { this.props.col.setColor("rgb(241, 239, 235)"); this.closeResultsVisibility = "none"; } diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index d3c0bfba3..0d02a4388 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -94,15 +94,15 @@ export class FilterBox extends ViewBoxBaseComponent item === facetHeader)) !== -1) { - docFilter.splice(index, 3); + while ((index = docFilter.findIndex(item => item.split(":")[0] === facetHeader)) !== -1) { + docFilter.splice(index, 1); } } const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string")); if (docRangeFilters) { let index: number; - while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) { - docRangeFilters.splice(index, 3); + while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === facetHeader)) !== -1) { + docRangeFilters.splice(index, 1); } } } else { @@ -224,10 +224,10 @@ export class FilterBox extends ViewBoxBaseComponent { - for (let i = 0; i < docFilters.length; i += 3) { - if (docFilters[i] === key && (docFilters[i + 1] === value || modifiers === "match" || modifiers === "remove")) { - if (docFilters[i + 2] === modifiers && modifiers && docFilters[i + 1] === value) return; - docFilters.splice(i, 3); + for (let i = 0; i < docFilters.length; i++) { + const fields = docFilters[i].split(":"); // split key:value:modifier + if (fields[0] === key && (fields[1] === value || modifiers === "match" || modifiers === "remove")) { + if (fields[2] === modifiers && modifiers && fields[1] === value) return; + docFilters.splice(i, 1); container._docFilters = new List(docFilters); break; } @@ -1065,9 +1066,7 @@ export namespace Doc { if (!docFilters.length && modifiers === "match" && value === undefined) { container._docFilters = undefined; } else if (modifiers !== "remove") { - docFilters.push(key); - docFilters.push(value); - docFilters.push(modifiers); + docFilters.push(key + ":" + value + ":" + modifiers); container._docFilters = new List(docFilters); } } diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 03795d254..1e02b9e58 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -276,7 +276,7 @@ export namespace WebSocket { const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; const newListItems = diff.diff.$set[updatefield].fields; const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields || []; - diff.diff.$set[updatefield].fields = [...curList, ...newListItems.filter((newItem: any) => !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : false))]; + diff.diff.$set[updatefield].fields = [...curList, ...newListItems.filter((newItem: any) => !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))]; const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; delete diff.diff.length; Database.Instance.update(diff.id, diff.diff, @@ -295,7 +295,7 @@ export namespace WebSocket { const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; const remListItems = diff.diff.$set[updatefield].fields; const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields || []; - diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : false)); + diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)); const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; delete diff.diff.length; Database.Instance.update(diff.id, diff.diff, -- cgit v1.2.3-70-g09d2 From 65448167a4db396b2ee3c56d870233f8ac182eb6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 11 Oct 2020 01:33:28 -0400 Subject: prevented documents that are unshared (ie private to soeone else) from showing up as an empty key value in stacking and other views. tried to fix an exception when showing links (that wasn't repeatable, so dion't know if fix works). --- src/client/views/collections/CollectionSubView.tsx | 2 +- .../CollectionFreeFormLinkView.tsx | 28 ++++++++++++---------- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index fa80c8062..493018093 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -134,7 +134,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : []; } - const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc); + const docs = rawdocs.filter(d => !(d instanceof Promise) && Object.keys(d).length).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index f051d5f8d..4cf257640 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -100,13 +100,15 @@ export class CollectionFreeFormLinkView extends React.Component width !== undefined && setTimeout(() => this.tryUpdateHeight(), 0) ); this._disposers.height = reaction( - () => NumCast(this.layoutDoc._height), + () => Cast(this.layoutDoc._height, "number", null), action(height => { if (height !== undefined && height <= 20 && height < NumCast(this.layoutDoc._delayAutoHeight, 20)) { this.layoutDoc._delayAutoHeight = height; -- cgit v1.2.3-70-g09d2 From 3256d0f42401b8dd8745e7f32c25033e118b444b Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 11 Oct 2020 17:22:03 -0400 Subject: removed setting fields to undefined when Acl is Private in hopes of not losing data when permissions are changed. prevented server writes on startup by making renderContentBounds setting a reaction, and making linearView not resize if documents are still promises and make text boxes not flip-flop their height all the time. --- src/client/views/collections/CollectionLinearView.tsx | 4 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 +++++++++++++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 +-- src/fields/Doc.ts | 10 +++++----- 4 files changed, 21 insertions(+), 11 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 859ee9362..0eac5136a 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -38,8 +38,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { componentDidMount() { // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). - this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.linearViewIsExpanded ? 1 : 0), - () => this.props.Document._width = 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10), + this._widthDisposer = reaction(() => 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10), + width => this.childDocs.length && (this.props.Document._width = width), { fireImmediately: true } ); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d48961849..4d9906f93 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -7,7 +7,7 @@ import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; -import { createSchema, makeInterface } from "../../../../fields/Schema"; +import { createSchema, makeInterface, listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { TraceMobx } from "../../../../fields/util"; @@ -88,6 +88,7 @@ export class CollectionFreeFormView extends CollectionSubView(); private _layoutSizeData = new ObservableMap(); private _cachedPool: Map = new Map(); @@ -1159,12 +1160,22 @@ export class CollectionFreeFormView extends CollectionSubView this.doLayoutComputation, (elements) => this._layoutElements = elements || [], { fireImmediately: true, name: "doLayout" }); + if (!this.props.annotationsKey) { + this._boundsReaction = reaction(() => this.contentBounds, + bounds => (!this.fitToContent && this._layoutElements?.length) && setTimeout(() => { + const rbounds = Cast(this.Document._renderContentBounds, listSpec("number"), [0, 0, 0, 0]); + if (rbounds[0] !== bounds.x || rbounds[1] !== bounds.y || rbounds[2] !== bounds.r || rbounds[3] !== bounds.b) { + this.Document._renderContentBounds = new List([bounds.x, bounds.y, bounds.r, bounds.b]); + } + })); + } this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); } componentWillUnmount() { this._layoutComputeReaction?.(); + this._boundsReaction?.(); this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); } @@ -1438,10 +1449,10 @@ export class CollectionFreeFormView extends CollectionSubView this.Document._renderContentBounds = new List([this.contentBounds.x, this.contentBounds.y, this.contentBounds.r, this.contentBounds.b]), 0); return
limitHeight) { @@ -1545,7 +1544,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.rootDoc[this.fieldKey + "-height"] = finalHeight; } catch (e) { console.log("Error in tryUpdateHeight"); } } - } + } else this.rootDoc[this.fieldKey + "-height"] = 0; } @computed get audioHandle() { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 7dd894ec4..086b7777f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -240,11 +240,11 @@ export class Doc extends RefField { if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { DocServer.GetRefField(this[Id], true); } - if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) { - this[UpdatingFromServer] = true; - this[FieldsSym](true); - this[UpdatingFromServer] = false; - } + // if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) { + // this[UpdatingFromServer] = true; + // this[FieldsSym](true); + // this[UpdatingFromServer] = false; + // } }; if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; -- cgit v1.2.3-70-g09d2 From 27635402ad810b910557eb1a86c7e85fa281aaee Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Oct 2020 01:05:13 -0400 Subject: switched fonticonbox and colelctionsubview to test GetEffectiveAcl to determine if document is available (instead of hack of testing 'author'). made GetEffectiveAcl a computedFn. No longer create a pushpin when an annotation that's linked to text is dragged off a PDF. fixed undo of pushpin navigation (used to call finish() twice). fixed pushpin navigation to conistenly pan & sensibly toggle target --- src/client/util/DocumentManager.ts | 9 +++++---- src/client/views/collections/CollectionSubView.tsx | 5 +++-- src/client/views/collections/CollectionView.tsx | 3 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 21 +++++++++++++-------- src/client/views/nodes/FontIconBox.tsx | 5 +++-- src/client/views/pdf/Annotation.tsx | 2 ++ src/client/views/pdf/PDFViewer.tsx | 2 +- src/fields/util.ts | 14 ++++++++++---- 8 files changed, 39 insertions(+), 22 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 178daf5f0..b37181e57 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -164,13 +164,14 @@ export class DocumentManager { const sameContext = annotatedDoc && annotatedDoc === originatingDoc?.context; if (originatingDoc?.isPushpin) { const hide = !docView.props.Document.hidden; - (!hide || !sameContext) && (docView.props.Document.hidden = !docView.props.Document.hidden); - docView.props.focus(docView.props.Document, willZoom, undefined, (notfocused: boolean) => { // bcz: Argh! TODO: Need to add a notFocused argument to the after finish callback function that indicates whether the window had to scroll to show the target - notfocused && hide && (docView.props.Document.hidden = true); + docView.props.focus(docView.props.Document, willZoom, undefined, (notfocused: boolean) => { // bcz: Argh! TODO: Need to add a notFocused argument to the after finish callback function that indicates whether the window had to scroll to show the target + if (notfocused || docView.props.Document.hidden) { + docView.props.Document.hidden = !docView.props.Document.hidden; + } return focusAndFinish(); // @ts-ignore bcz: Argh TODO: Need to add a parameter to focus() everywhere for whether focus should center the target's container in the view or not. // here we don't want to focus the container if the source and target are in the same container }, sameContext); - finished?.(); + //finished?.(); } else { docView.select(false); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 493018093..b282d1e27 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -103,7 +103,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: const { Document, DataDoc } = this.props; const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)). filter(pair => { // filter out any documents that have a proto that we don't have permissions to (which we determine by not having any keys - return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && Object.keys(pair.layout.proto).length)); + return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));// Object.keys(pair.layout.proto).length)); }); return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types } @@ -134,7 +134,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : []; } - const docs = rawdocs.filter(d => !(d instanceof Promise) && Object.keys(d).length).map(d => d as Doc); + const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(d) !== AclPrivate).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; @@ -502,4 +502,5 @@ import { SelectionManager } from "../../util/SelectionManager"; import { OverlayView } from "../OverlayView"; import { setTimeout } from "timers"; import { Hypothesis } from "../../util/HypothesisUtils"; +import { GetEffectiveAcl } from "../../../fields/util"; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 80e9b41ad..cfd24545b 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -159,7 +159,8 @@ export class CollectionView extends Touchable [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document const context = Cast(doc.context, Doc, null); - if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { + const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); + if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { const pushpin = Docs.Create.FontIconDocument({ title: "pushpin", label: "", icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7", diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4d9906f93..4df90e8ea 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -893,21 +893,24 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(doc.y)) { - scrollTo = NumCast(doc.y); + scrollTo = Math.max(0, NumCast(doc.y) - 50); } - if (curScroll !== scrollTo) { + if (curScroll !== scrollTo || this.props.Document._viewTransition) { this.props.Document._scrollPY = this.props.Document._scrollY = scrollTo; delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0; - !dontCenter && delay && this.props.focus(this.props.Document); + !dontCenter && this.props.focus(this.props.Document); afterFocus && setTimeout(afterFocus, delay); + } else { + !dontCenter && delay && this.props.focus(this.props.Document); // @ts-ignore - } else afterFocus(true); // bcz: TODO Aragh -- need to add a parameter to afterFocus() functions to indicate whether the focus function didn't need to scroll + afterFocus(true); // bcz: TODO Aragh -- need to add a parameter to afterFocus() functions to indicate whether the focus function didn't need to scroll + + } } } else { @@ -929,14 +932,16 @@ export class CollectionFreeFormView extends CollectionSubView { - if (afterFocus?.()) { + // @ts-ignore + if (afterFocus?.(notFocused)) { // bcz: TODO Aragh -- need to add a parameter to afterFocus() functions to indicate whether the focus function didn't need to scroll this.Document._panX = savedState.px; this.Document._panY = savedState.py; this.Document[this.scaleFieldKey] = savedState.s; this.Document._viewTransition = savedState.pt; } - }, 500); + }, notFocused ? 0 : 500); } } diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 156256fe5..276c66bb1 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -8,11 +8,12 @@ import { FieldView, FieldViewProps } from './FieldView'; import { StrCast, Cast, ScriptCast } from '../../../fields/Types'; import { Utils, setupMoveUpEvents, returnFalse, emptyFunction } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer } from 'mobx'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, AclPrivate } from '../../../fields/Doc'; import { ContextMenu } from '../ContextMenu'; import { ScriptField } from '../../../fields/ScriptField'; import { Tooltip } from '@material-ui/core'; import { DragManager } from '../../util/DragManager'; +import { GetEffectiveAcl } from '../../../fields/util'; const FontIconSchema = createSchema({ icon: "string", }); @@ -105,7 +106,7 @@ export class FontIconBadge extends React.Component { render() { if (!(this.props.collection instanceof Doc)) return (null); - const length = DocListCast(this.props.collection.data).filter(d => Object.keys(d).length).length; // filter out any documents that we can't read + const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read return
0 ? { "display": "initial" } : { "display": "none" }} onPointerDown={this.onPointerDown} > diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index e7f901091..a071abd21 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -71,6 +71,7 @@ class RegionAnnotation extends React.Component { this._reactionDisposer && this._reactionDisposer(); } + @undoBatch deleteAnnotation = () => { const annotation = DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]); const group = FieldValue(Cast(this.props.document.group, Doc)); @@ -86,6 +87,7 @@ class RegionAnnotation extends React.Component { PDFMenu.Instance.fadeOut(true); } + @undoBatch pinToPres = () => { const group = FieldValue(Cast(this.props.document.group, Doc)); const isPinned = group && Doc.isDocPinned(group) ? true : false; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 78b95b385..d8be3defd 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -194,7 +194,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent= 0) { - if (!this._mainCont.current) setTimeout(() => smoothScroll(1000, this._mainCont.current!, 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; } diff --git a/src/fields/util.ts b/src/fields/util.ts index 881f301f3..b68d961b1 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -13,6 +13,7 @@ import { returnZero } from "../Utils"; import CursorField from "./CursorField"; import { List } from "./List"; import { SnappingManager } from "../client/util/SnappingManager"; +import { computedFn } from "mobx-utils"; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); @@ -139,7 +140,7 @@ export function denormalizeEmail(email: string) { * * Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document. * - * Add: a user with add access to a document can add documents/annotations to that document but cannot edit or delete anything. + * Add: a user with add access to a document can augment documents/annotations to that document but cannot edit or delete anything. * * View: a user with view access to a document can only view it - they cannot add/remove/edit anything. * @@ -148,7 +149,7 @@ export function denormalizeEmail(email: string) { export enum SharingPermissions { Admin = "Admin", Edit = "Can Edit", - Add = "Can Add", + Add = "Can Augment", View = "Can View", None = "Not Shared" } @@ -157,6 +158,11 @@ export enum SharingPermissions { * Calculates the effective access right to a document for the current user. */ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, user?: string): symbol { + return computedFn(function (target: any, in_prop?: string | symbol | number, user?: string) { + return getEffectiveAcl(target, in_prop, user); + }, true)(target, in_prop, user); +} +function getEffectiveAcl(target: any, in_prop?: string | symbol | number, user?: string): symbol { if (!target) return AclPrivate; // all changes received fromt the server must be processed as Admin @@ -219,7 +225,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc const HierarchyMapping = new Map([ ["Not Shared", 0], ["Can View", 1], - ["Can Add", 2], + ["Can Augment", 2], ["Can Edit", 3], ["Admin", 4] ]); @@ -281,7 +287,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, "None"].includes(value))) return true; - // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true; + // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { if (!prop.startsWith("_")) { -- cgit v1.2.3-70-g09d2 From 9f6dba274539a78a541afd398c73a17dec996319 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Oct 2020 01:34:00 -0400 Subject: capped zoom in at a factor of 20. fixed zoom in not to pan when it hits its zoom in limit. --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4df90e8ea..71519f2b9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -783,10 +783,14 @@ export class CollectionFreeFormView extends CollectionSubView 0 ? (1 / 1.05) : 1.05; if (deltaScale < 0) deltaScale = -deltaScale; const [x, y] = this.getTransform().transformPoint(pointX, pointY); + const invTransform = this.getLocalTransform().inverse(); + if (deltaScale * invTransform.Scale > 20) { + deltaScale = 20 / invTransform.Scale; + } const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y); if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) { - const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40); + const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 20); this.props.Document[this.scaleFieldKey] = Math.abs(safeScale); this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); } -- cgit v1.2.3-70-g09d2 From 04bbff759710d58fa97ce2f0d685ec59b6beb60e Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Oct 2020 12:53:40 -0400 Subject: split getEffectiveAcl code for determining if a field can be modified out into getPropAcl. --- src/client/util/SharingManager.tsx | 4 +-- src/client/views/DocComponent.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 +- src/fields/util.ts | 44 ++++++++++++++----------- 4 files changed, 28 insertions(+), 24 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 0c8f19eae..914253e3c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -177,7 +177,7 @@ export class SharingManager extends React.Component<{}> { users.forEach(({ user, sharingDoc }) => { if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists }); } }); @@ -273,7 +273,7 @@ export class SharingManager extends React.Component<{}> { distributeAcls(acl, permission as SharingPermissions, doc); if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); - else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); + else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); }); } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 98e888538..a55f4adaf 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -164,7 +164,7 @@ export function ViewBoxAnnotatableComponent

{ for (const [key, value] of Object.entries(this.props.Document[AclSym])) { if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index cfd24545b..a27fa5a66 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -140,7 +140,7 @@ export class CollectionView extends Touchable { for (const [key, value] of Object.entries(this.props.Document[AclSym])) { if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); diff --git a/src/fields/util.ts b/src/fields/util.ts index 56736028a..dd0444d61 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -155,32 +155,35 @@ export enum SharingPermissions { } // return acl from cache or cache the acl and return. bcz: Argh! NOT WORKING ... nothing gets invalidated properly.... -const getEffectiveAclCache = computedFn(function (target: any, playgroundProp: boolean, user?: string) { return getEffectiveAcl(target, playgroundProp, user); }, true); +const getEffectiveAclCache = computedFn(function (target: any, user?: string) { return getEffectiveAcl(target, user); }, true); /** * Calculates the effective access right to a document for the current user. */ -export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, user?: string): symbol { - if (!target) return AclPrivate; - if (in_prop === UpdatingFromServer) return AclAdmin; // requesting the UpdatingFromServer prop must always go through to keep the local DB consistent - const playgroundProp = in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()) ? true : false; - return getEffectiveAcl(target, playgroundProp, user); +export function GetEffectiveAcl(target: any, user?: string): symbol { + return target ? getEffectiveAcl(target, user) : AclPrivate; } -function getEffectiveAcl(target: any, playgroundProp: boolean, user?: string): symbol { +function getPropAcl(target: any, prop: string | symbol | number) { + if (prop === UpdatingFromServer) return AclAdmin; // requesting the UpdatingFromServer prop 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); +} + +let HierarchyMapping: Map | undefined; + +function getEffectiveAcl(target: any, user?: string): symbol { if (target[UpdatingFromServer]) return AclAdmin; // all changes received from the server must be processed as Admin // if the current user is the author of the document / the current user is a member of the admin group const userChecked = user || Doc.CurrentUserEmail; - if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; + if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; // target may be a Doc of Proxy, so check __fields.author and .author if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin; + const targetAcls = target[AclSym]; - if (target[AclSym] && Object.keys(target[AclSym]).length) { + if (targetAcls && Object.keys(targetAcls).length) { + if (_overrideAcl) return AclEdit; - // if the acl is being overriden or the property being modified is one of the playground fields (which can be freely modified) - if (_overrideAcl || playgroundProp) return AclEdit; - - let effectiveAcl = AclPrivate; - const HierarchyMapping = new Map([ + HierarchyMapping = HierarchyMapping || new Map([ [AclPrivate, 0], [AclReadonly, 1], [AclAddonly, 2], @@ -188,12 +191,13 @@ function getEffectiveAcl(target: any, playgroundProp: boolean, user?: string): s [AclAdmin, 4] ]); - for (const [key, value] of Object.entries(target[AclSym])) { + let effectiveAcl = AclPrivate; + for (const [key, value] of Object.entries(targetAcls)) { // there are issues with storing fields with . in the name, so they are replaced with _ during creation // as a result we need to restore them again during this comparison. const entity = denormalizeEmail(key.substring(4)); // an individual or a group - if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) { - if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { + if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { + if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) { effectiveAcl = value as symbol; if (effectiveAcl === AclAdmin) return effectiveAcl; } @@ -201,8 +205,8 @@ function getEffectiveAcl(target: any, playgroundProp: boolean, user?: string): s } // if there's an overriding acl set through the properties panel or sharing menu, that's what's returned if the user isn't an admin of the document - const override = target[AclSym]["acl-Override"]; - if (override !== AclUnset && override !== undefined) effectiveAcl = target[AclSym]["acl-Override"]; + const override = targetAcls["acl-Override"]; + if (override !== AclUnset && override !== undefined) effectiveAcl = override; // if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl) return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)! < 3 ? AclEdit : effectiveAcl; @@ -282,7 +286,7 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"]; export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; - const effectiveAcl = GetEffectiveAcl(target, in_prop); + const effectiveAcl = getPropAcl(target, prop); if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true; // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't -- cgit v1.2.3-70-g09d2 From 8ebf3cb0ac7a023aa47a5264d74c3edaebf28b1b Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Oct 2020 17:22:26 -0400 Subject: updated getEffectiveAcl to be a computedFn. got rid of OverrideAcl in favor of just using UpdatingFromServer --- src/client/DocServer.ts | 2 +- src/client/util/SerializationHelper.ts | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 ++--- src/fields/Doc.ts | 20 +++++++----- src/fields/util.ts | 36 +++++++++------------- 6 files changed, 33 insertions(+), 37 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index d936f6e2a..00f9877c3 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,6 +1,6 @@ import * as io from 'socket.io-client'; import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message"; -import { Opt, Doc, fetchProto, FieldsSym, UpdatingFromServer } from '../fields/Doc'; +import { Opt, Doc, UpdatingFromServer } from '../fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../fields/RefField'; diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 19b217726..00ac6e521 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -43,7 +43,7 @@ export namespace SerializationHelper { } if (!obj.__type) { - if (ClientUtils.RELEASE) { + if (true || ClientUtils.RELEASE) { console.warn("No property 'type' found in JSON."); return undefined; } else { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index b282d1e27..f3e563422 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -134,7 +134,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: rawdocs = rootDoc && !this.props.annotationsKey ? [Doc.GetProto(rootDoc)] : []; } - const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(d) !== AclPrivate).map(d => d as Doc); + const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index da797eda0..4f8f46111 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -11,7 +11,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin, UpdatingFromServer } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); import { removeMarkWithAttrs } from "./prosemirrorPatches"; @@ -22,7 +22,7 @@ import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types"; -import { TraceMobx, OVERRIDE_acl, GetEffectiveAcl } from '../../../../fields/util'; +import { TraceMobx, GetEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -805,9 +805,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp tr = tr.addMark(pos, pos + node.nodeSize, link); } }); - OVERRIDE_acl(true); + this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); - OVERRIDE_acl(false); + this.dataDoc[UpdatingFromServer] = false; } } componentDidMount() { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 2452ab408..cea09b9c5 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -102,22 +102,28 @@ const AclMap = new Map([ [SharingPermissions.Admin, AclAdmin] ]); -export function fetchProto(doc: Doc) { +// caches the document access permissions for the current user. +// this recursively updates all protos as well. +export function updateCachedAcls(doc: Doc) { if (!doc) return; const permissions: { [key: string]: symbol } = {}; + doc[UpdatingFromServer] = true; Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!)); + doc[UpdatingFromServer] = false; - if (Object.keys(permissions).length) doc[AclSym] = permissions; + if (Object.keys(permissions).length) { + doc[AclSym] = permissions; + } if (doc.proto instanceof Promise) { - doc.proto.then(fetchProto); + doc.proto.then(updateCachedAcls); return doc.proto; } } @scriptingGlobal -@Deserializable("Doc", fetchProto).withFields(["id"]) +@Deserializable("Doc", updateCachedAcls).withFields(["id"]) export class Doc extends RefField { constructor(id?: FieldId, forceSave?: boolean) { super(id); @@ -233,17 +239,15 @@ export class Doc extends RefField { const prev = GetEffectiveAcl(this); this[UpdatingFromServer] = true; this[fKey] = value; + this[UpdatingFromServer] = false; if (fKey.startsWith("acl")) { - fetchProto(this); + updateCachedAcls(this); } - this[UpdatingFromServer] = false; if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { DocServer.GetRefField(this[Id], true); } // if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) { - // this[UpdatingFromServer] = true; // this[FieldsSym](true); - // this[UpdatingFromServer] = false; // } }; if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { diff --git a/src/fields/util.ts b/src/fields/util.ts index dd0444d61..d48011194 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,14 +1,14 @@ import { UndoManager } from "../client/util/UndoManager"; -import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto, AclUnset, DocListCastAsync } from "./Doc"; +import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; -import { action, trace, observable, reaction, computed } from "mobx"; -import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate, ToString, ToScriptString } from "./FieldSymbols"; +import { action, trace, } from "mobx"; +import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols"; import { DocServer } from "../client/DocServer"; import { ComputedField } from "./ScriptField"; -import { ScriptCast, StrCast, DateCast, Cast, NumCast } from "./Types"; +import { ScriptCast, StrCast } from "./Types"; import { returnZero } from "../Utils"; import CursorField from "./CursorField"; import { List } from "./List"; @@ -114,10 +114,6 @@ export function makeReadOnly() { export function makeEditable() { _setter = _setterImpl; } -var _overrideAcl = false; -export function OVERRIDE_acl(val: boolean) { - _overrideAcl = val; -} export function normalizeEmail(email: string) { return email.replace(/\./g, '__'); @@ -154,18 +150,19 @@ export enum SharingPermissions { None = "Not Shared" } -// return acl from cache or cache the acl and return. bcz: Argh! NOT WORKING ... nothing gets invalidated properly.... +// return acl from cache or cache the acl and return. const getEffectiveAclCache = computedFn(function (target: any, user?: string) { return getEffectiveAcl(target, user); }, true); /** * Calculates the effective access right to a document for the current user. */ export function GetEffectiveAcl(target: any, user?: string): symbol { - return target ? getEffectiveAcl(target, user) : AclPrivate; + return !target ? AclPrivate : + target[UpdatingFromServer] ? AclAdmin : getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } function getPropAcl(target: any, prop: string | symbol | number) { - if (prop === UpdatingFromServer) return AclAdmin; // requesting the UpdatingFromServer prop 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); } @@ -173,16 +170,12 @@ function getPropAcl(target: any, prop: string | symbol | number) { let HierarchyMapping: Map | undefined; function getEffectiveAcl(target: any, user?: string): symbol { - if (target[UpdatingFromServer]) return AclAdmin; // all changes received from the server must be processed as Admin - // if the current user is the author of the document / the current user is a member of the admin group - const userChecked = user || Doc.CurrentUserEmail; + const targetAcls = target[AclSym]; + const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; // target may be a Doc of Proxy, so check __fields.author and .author if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin; - const targetAcls = target[AclSym]; if (targetAcls && Object.keys(targetAcls).length) { - if (_overrideAcl) return AclEdit; - HierarchyMapping = HierarchyMapping || new Map([ [AclPrivate, 0], [AclReadonly, 1], @@ -199,7 +192,6 @@ function getEffectiveAcl(target: any, user?: string): symbol { if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) { effectiveAcl = value as symbol; - if (effectiveAcl === AclAdmin) return effectiveAcl; } } } @@ -278,8 +270,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc }); } - layoutDocChanged && fetchProto(target); // updates target[AclSym] when changes to acls have been made - dataDocChanged && fetchProto(dataDoc); + layoutDocChanged && updateCachedAcls(target); // updates target[AclSym] when changes to acls have been made + dataDocChanged && updateCachedAcls(dataDoc); } const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox", @@ -312,9 +304,9 @@ export function setter(target: any, in_prop: string | symbol | number, value: an export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { let prop = in_prop; - if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym]; + if (in_prop === AclSym) return target[AclSym]; if (in_prop === "toString" || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === "symbol")) return target.__fields[prop] || target[prop]; - if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return prop === HeightSym || prop === WidthSym ? returnZero : undefined; + if (GetEffectiveAcl(target) === AclPrivate) return prop === HeightSym || prop === WidthSym ? returnZero : undefined; if (prop === LayoutSym) return target.__LAYOUT__; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { if (!prop.startsWith("_")) { -- cgit v1.2.3-70-g09d2 From 80ccba05db32ca09ac513f3ec370f3deef873ce7 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Wed, 14 Oct 2020 02:45:39 +0800 Subject: update view (as button), doc.dragging q? --- .../views/collections/CollectionStackingView.tsx | 4 +-- src/client/views/nodes/PresBox.tsx | 33 ++++++++++++++--- .../views/presentationview/PresElementBox.tsx | 42 ++++++++++++++++------ 3 files changed, 61 insertions(+), 18 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index d41e0528d..3607b97d0 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -295,7 +295,7 @@ export class CollectionStackingView extends CollectionSubView { if (i === 0) { - doc.dragging = false; + 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]); @@ -303,7 +303,7 @@ export class CollectionStackingView extends CollectionSubView srcInd ? targInd - 1 : targInd) + plusOne, 0, doc); } else if (i < (newDocs.length / 2)) { //glr: for some reason dragged documents are duplicated - doc.dragging = false; + if (doc.presentationTargetDoc) doc.dragging = false; DragManager.docsBeingDragged = []; if (targInd === -1) targInd = docs.length; else targInd = docs.indexOf(newDocs[0]) + 1; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 1a16e6237..8e2884968 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1250,10 +1250,10 @@ export class PresBox extends ViewBoxBaseComponent const currentFrame = Cast(tagDoc._currentFrame, "number", null); if (currentFrame === undefined) { tagDoc._currentFrame = 0; - // CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); + CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } - // if (tagDoc.editScrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); + if (tagDoc.editScrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); @@ -1314,7 +1314,7 @@ export class PresBox extends ViewBoxBaseComponent

Contents
Edit
-
+ {/*
Active text color
{ this.openActiveColorPicker = !this.openActiveColorPicker; })}>
@@ -1325,7 +1325,7 @@ export class PresBox extends ViewBoxBaseComponent
this.openViewedColorPicker = !this.openViewedColorPicker)}>
- {this.viewedColorPicker} + {this.viewedColorPicker} */} {/*
Zoom
Edit
@@ -1352,6 +1352,14 @@ export class PresBox extends ViewBoxBaseComponent
{"Last frame"}
}>
{NumCast(targetDoc.lastFrame)}
+
+
+ +
+
+ {this.frameList} +
+
console.log(" TODO: play frames")}>Play
@@ -1454,7 +1462,7 @@ export class PresBox extends ViewBoxBaseComponent activeItem.scrollProgressivize = !activeItem.scrollProgressivize; const targetDoc: Doc = this.targetDoc; targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize; - // CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); + CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); if (targetDoc.editScrollProgressivize) { targetDoc.editScrollProgressivize = false; targetDoc._currentFrame = 0; @@ -1715,6 +1723,21 @@ export class PresBox extends ViewBoxBaseComponent ); } + @computed get frameList() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + const frameList: number[] = []; + frameList.length = NumCast(targetDoc.lastFrame); + const frameItems = frameList.map((value) => { +
+ +
+ }) + return
+ {frameItems} +
; + } + @computed get playButtonFrames() { const targetDoc: Doc = this.targetDoc; return ( diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index b1eac5832..367bc6533 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -71,10 +71,10 @@ export class PresElementBox extends ViewBoxBaseComponent 97; + embedHeight = (): number => 97; // embedWidth = () => this.props.PanelWidth(); // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight); - embedWidth = () => this.props.PanelWidth() - 30; + embedWidth = (): number => this.props.PanelWidth() - 30; /** * The function that is responsible for rendering a preview or not for this * presentation element. @@ -155,7 +155,14 @@ export class PresElementBox extends ViewBoxBaseComponent) => { + e.stopPropagation(); + e.preventDefault(); + } + + stopDrag = (e: PointerEvent) => { + console.log('stop drag') const activeItem = this.rootDoc; + activeItem.dragging = false; e.stopPropagation(); e.preventDefault(); } @@ -184,7 +191,7 @@ export class PresElementBox extends ViewBoxBaseComponent { const slide = this._itemRef.current!; - const rect = slide!.getBoundingClientRect(); + const rect = slide?.getBoundingClientRect(); let y = e.clientY - rect.top; //y position within the element. let height = slide.clientHeight; let halfLine = height / 2; @@ -201,9 +208,8 @@ export class PresElementBox extends ViewBoxBaseComponent { - console.log('pointerLeave'); - this._itemRef.current!.style.borderTop = "0px" - this._itemRef.current!.style.borderBottom = "0px" + this._itemRef.current!.style.borderTop = "0px"; + this._itemRef.current!.style.borderBottom = "0px"; document.removeEventListener("pointermove", this.onPointerMove); } @@ -240,6 +246,10 @@ export class PresElementBox extends ViewBoxBaseComponent= 300; + const targetDoc: Doc = Cast(this.rootDoc.presentationTargetDoc, Doc, null); + const activeItem: Doc = this.rootDoc; return (
{ - console.log('double click to open'); this.toggleProperties(); this.props.focus(this.rootDoc); this.clearArrays(); @@ -274,7 +283,7 @@ export class PresElementBox extends ViewBoxBaseComponent
-
+
{isSelected ? -
{"Movement speed"}
}>
300 ? "block" : "none" }}>{this.transition}
-
{"Duration"}
}>
300 ? "block" : "none" }}>{this.duration}
-
{"Presentation pin view"}
}>
300 ? "block" : "none" }}>V
+
{"Movement speed"}
}>
{this.transition}
+
{"Duration"}
}>
{this.duration}
+
{"Update view"}
}> +
{ + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + }} + style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V
+
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> e.stopPropagation()} />
-- cgit v1.2.3-70-g09d2 From 072d1aa988f36220e4b74c0ee5413f0597a4b619 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 13 Oct 2020 16:57:25 -0400 Subject: prevent zooming out on annotation overlays beyond 1-1 --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 71519f2b9..f075aefe1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -789,7 +789,7 @@ export class CollectionFreeFormView extends CollectionSubView= 0.15 || localTransform.Scale > this.zoomScaling()) { + if ((localTransform.Scale > 1 || !this.props.annotationsKey) && (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling())) { const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 20); this.props.Document[this.scaleFieldKey] = Math.abs(safeScale); this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); -- cgit v1.2.3-70-g09d2 From 79221b73d0526ce31349cf4a5b75f0047c60b829 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 14 Oct 2020 11:02:40 -0400 Subject: fixed focusDocument in freeform views to work with annotation overlays(mostly) - still an issue when original image is in a frame that doen't match its dimensions --- src/client/util/CurrentUserUtils.ts | 1 + src/client/views/collections/TabDocView.tsx | 12 +++++++----- .../collectionFreeForm/CollectionFreeFormView.tsx | 19 +++++++++++-------- src/client/views/nodes/ImageBox.tsx | 16 +++++++++------- 4 files changed, 28 insertions(+), 20 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7535d7c24..dcbeba8cd 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -881,6 +881,7 @@ export class CurrentUserUtils { let linkDocs = await DocServer.GetRefField(linkDatabaseId); if (!linkDocs) { linkDocs = new Doc(linkDatabaseId, true); + (linkDocs as Doc).author = Doc.CurrentUserEmail; (linkDocs as Doc).data = new List([]); (linkDocs as Doc)["acl-Public"] = SharingPermissions.Add; } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 70eb5b895..0c7f39dc7 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -45,6 +45,8 @@ export class TabDocView extends React.Component { @observable private _document: Doc | undefined; @observable private _view: DocumentView | undefined; + @computed get contentScaling() { return this.ContentScaling(); } + get stack(): any { return (this.props as any).glContainer.parent.parent; } get tab() { return (this.props as any).glContainer.tab; } get view() { return this._view; } @@ -194,7 +196,7 @@ export class TabDocView extends React.Component { panelHeight = () => this.nativeAspect() && this.nativeAspect() > this._panelWidth / this._panelHeight ? this._panelWidth / this.nativeAspect() : this._panelHeight; nativeWidth = () => !this.layoutDoc?._fitWidth ? NumCast(this.layoutDoc?._nativeWidth) || this._panelWidth : 0; nativeHeight = () => !this.layoutDoc?._fitWidth ? NumCast(this.layoutDoc?._nativeHeight) || this._panelHeight : 0; - contentScaling = () => { + ContentScaling = () => { const nativeH = NumCast(this.layoutDoc?._nativeHeight); const nativeW = NumCast(this.layoutDoc?._nativeWidth); let scaling = 1; @@ -210,12 +212,12 @@ export class TabDocView extends React.Component { if (this._mainCont?.children) { const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0]?.firstChild as HTMLElement); const scale = Utils.GetScreenTransform(this._mainCont).scale; - return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); + return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.ContentScaling() / scale); } return Transform.Identity(); } - @computed get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; } - @computed get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.contentScaling()) / this._panelWidth * 100}% ` : undefined; } + @computed get previewPanelCenteringOffset() { return this.nativeWidth() ? (this._panelWidth - this.nativeWidth() * this.ContentScaling()) / 2 : 0; } + @computed get widthpercent() { return this.nativeWidth() ? `${(this.nativeWidth() * this.ContentScaling()) / this._panelWidth * 100}% ` : undefined; } @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } // adds a tab to the layout based on the locaiton parameter which can be: @@ -337,7 +339,7 @@ export class TabDocView extends React.Component { rootSelected={returnTrue} addDocument={undefined} removeDocument={undefined} - ContentScaling={this.contentScaling} + ContentScaling={this.ContentScaling} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} NativeHeight={this.nativeHeight() ? this.nativeHeight : undefined} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f075aefe1..8b9e84bd6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -787,9 +787,12 @@ export class CollectionFreeFormView extends CollectionSubView 20) { deltaScale = 20 / invTransform.Scale; } + if (deltaScale * invTransform.Scale < 1 && this.isAnnotationOverlay) { + deltaScale = 1 / invTransform.Scale; + } const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y); - if ((localTransform.Scale > 1 || !this.props.annotationsKey) && (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling())) { + if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) { const safeScale = Math.min(Math.max(0.15, localTransform.Scale), 20); this.props.Document[this.scaleFieldKey] = Math.abs(safeScale); this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); @@ -919,21 +922,21 @@ export class CollectionFreeFormView extends CollectionSubView { - const pw = this.props.PanelWidth(); - const ph = this.props.PanelHeight(); + const pw = this.isAnnotationOverlay ? NumCast(this.props.Document._nativeWidth) : this.props.PanelWidth(); + const ph = this.isAnnotationOverlay ? NumCast(this.props.Document._nativeHeight) : this.props.PanelHeight(); pw && ph && (this.Document[this.scaleFieldKey] = scale * Math.min(pw / NumCast(doc._width), ph / NumCast(doc._height))); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 164456cfb..bc38a17eb 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -67,6 +67,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent { this._dropDisposer?.(); ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); @@ -266,7 +268,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { const remoteUrl = this.dataDoc.googlePhotosUrl; return !remoteUrl ? (null) : ( window.open(remoteUrl)} @@ -291,7 +293,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { const { dataDoc } = this; @@ -402,18 +404,18 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.props.ScreenToLocalTransform().translate(0, -this.ycenter / this.props.ContentScaling()); + screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.ycenter / this.contentScaling); contentFunc = () => [this.content]; render() { TraceMobx(); return (
Date: Thu, 15 Oct 2020 13:26:04 +0800 Subject: pin with view on scrollable documents and some undo bug fixes --- src/client/views/collections/CollectionMenu.tsx | 41 ++++++++++++++-------- src/client/views/nodes/PresBox.tsx | 32 ++++++++++------- .../views/presentationview/PresElementBox.tsx | 28 ++++++++++----- 3 files changed, 64 insertions(+), 37 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index bf6067978..d9924c299 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -394,28 +394,39 @@ export class CollectionViewBaseChrome extends React.Component; } + @undoBatch + @action + pinWithView = (targetDoc: Opt) => { + if (targetDoc) { + TabDocView.PinDoc(targetDoc, false); + const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; + if (targetDoc.type === DocumentType.PDF) { + const scroll = targetDoc._scrollTop; + activeDoc.presPinView = true; + activeDoc.presPinViewScroll = scroll; + } else { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeDoc.presPinView = true; + activeDoc.presPinViewX = x; + activeDoc.presPinViewY = y; + activeDoc.presPinViewScale = scale; + } + } + } + @computed get pinWithViewButton() { const presPinWithViewIcon = ; const targetDoc = this.selectedDoc; - return (!targetDoc || (targetDoc._viewType !== CollectionViewType.Freeform && targetDoc.type !== DocumentType.IMG)) ? (null) :
{"Pin to presentation trail with current view"}
} placement="top"> + {/* return (!targetDoc || (targetDoc._viewType !== CollectionViewType.Freeform && targetDoc.type !== DocumentType.IMG)) ? (null) :
{"Pin to presentation trail with current view"}
} placement="top"> */ } + return (targetDoc && (targetDoc._viewType === CollectionViewType.Freeform || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF)) ?
{"Pin to presentation trail with current view"}
} placement="top"> -
; +
: (null); } diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index dd7eba0b2..36705ecba 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -299,10 +299,14 @@ export class PresBox extends ViewBoxBaseComponent // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; bestTarget && runInAction(() => { - bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - bestTarget._panX = activeItem.presPinViewX; - bestTarget._panY = activeItem.presPinViewY; - bestTarget._viewScale = activeItem.presPinViewScale; + if (bestTarget.type === DocumentType.PDF) { + bestTarget._scrollY = activeItem.presPinViewScroll; + } else { + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; + bestTarget._panX = activeItem.presPinViewX; + bestTarget._panY = activeItem.presPinViewY; + bestTarget._viewScale = activeItem.presPinViewScale; + } }); //setTimeout(() => targetDoc._viewTransition = undefined, 1010); } @@ -451,6 +455,8 @@ export class PresBox extends ViewBoxBaseComponent srcContext.presPathView = this.pathBoolean; } } + + @undoBatch @action toggleExpandMode = () => { this.rootDoc.expandBoolean = !this.rootDoc.expandBoolean; this.childDocs.forEach((doc) => { @@ -890,9 +896,9 @@ export class PresBox extends ViewBoxBaseComponent
Visibility {"&"} Duration
-
{"Hide before presented"}
}>
activeItem.presHideTillShownButton = !activeItem.presHideTillShownButton}>Hide before
-
{"Hide after presented"}
}>
activeItem.presHideAfterButton = !activeItem.presHideAfterButton}>Hide after
-
{"Open document in a new tab"}
}>
activeItem.openDocument = !activeItem.openDocument}>Open
+
{"Hide before presented"}
}>
activeItem.presHideTillShownButton = !activeItem.presHideTillShownButton)}>Hide before
+
{"Hide after presented"}
}>
activeItem.presHideAfterButton = !activeItem.presHideAfterButton)}>Hide after
+
{"Open document in a new tab"}
}>
activeItem.openDocument = !activeItem.openDocument)}>Open
Slide Duration
@@ -924,12 +930,12 @@ export class PresBox extends ViewBoxBaseComponent {effect}
e.stopPropagation()}> -
e.stopPropagation()} onClick={() => targetDoc.presEffect = 'None'}>None
-
e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Fade'}>Fade In
-
e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Flip'}>Flip
-
e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Rotate'}>Rotate
-
e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Bounce'}>Bounce
-
e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Roll'}>Roll
+
e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'None')}>None
+
e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Fade')}>Fade In
+
e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Flip')}>Flip
+
e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Rotate')}>Rotate
+
e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Bounce')}>Bounce
+
e.stopPropagation()} onClick={undoBatch(() => targetDoc.presEffect = 'Roll')}>Roll
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index adaabec8a..9a6e4313d 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -242,6 +242,23 @@ export class PresElementBox extends ViewBoxBaseComponent { + console.log(targetDoc.type); + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { + const scroll = targetDoc._scrollTop; + activeItem.presPinViewScroll = scroll; + } else { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } + } + @computed get mainItem() { const isSelected: boolean = PresBox.Instance._selectedArray.includes(this.rootDoc); const isDragging: boolean = BoolCast(this.rootDoc.dragging); @@ -300,14 +317,7 @@ export class PresElementBox extends ViewBoxBaseComponent
{"Update view"}
}>
{ - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - }} + onClick={() => this.pinWithView(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> @@ -321,7 +331,7 @@ export class PresElementBox extends ViewBoxBaseComponent {this.renderEmbeddedInline}
-
); +
); } render() { -- cgit v1.2.3-70-g09d2