diff options
author | bobzel <zzzman@gmail.com> | 2020-10-10 12:10:48 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2020-10-10 12:10:48 -0400 |
commit | f3d04b73a53d7680092ce925fceede4f910df5bb (patch) | |
tree | f5b790ae556d2bdaeaa23c4cd0494c9a2b2e2cf1 /src | |
parent | ae8dafadcc0c6cea1ac087f17ac84bd302fca39e (diff) |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 14 | ||||
-rw-r--r-- | src/client/util/GroupManager.tsx | 5 | ||||
-rw-r--r-- | src/client/util/SharingManager.tsx | 28 | ||||
-rw-r--r-- | src/client/util/SnappingManager.ts | 8 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingView.tsx | 1 | ||||
-rw-r--r-- | src/fields/List.ts | 5 | ||||
-rw-r--r-- | src/fields/util.ts | 46 | ||||
-rw-r--r-- | src/server/websocket.ts | 6 |
8 files changed, 61 insertions, 52 deletions
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<Doc>).push(doc) : group.docsShared = new List<Doc>([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<StackingDocument, const docs = this.childDocList; if (docs) { newDocs.map((doc, i) => { - 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<Doc>(diff.items)) } } : - diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } - : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; + const op = + diff?.op === "$addToSet" ? { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } : + diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } + : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; + !op['$set'] && ((op as any).length = diff.length); + const oldValue = current; const 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) { |