diff options
Diffstat (limited to 'src/fields/util.ts')
| -rw-r--r-- | src/fields/util.ts | 196 |
1 files changed, 109 insertions, 87 deletions
diff --git a/src/fields/util.ts b/src/fields/util.ts index 36f619120..4dcbf1fbe 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,15 +1,13 @@ import { $mobx, action, observable, runInAction, trace } from 'mobx'; import { computedFn } from 'mobx-utils'; import { DocServer } from '../client/DocServer'; -import { CollectionViewType } from '../client/documents/DocumentTypes'; import { LinkManager } from '../client/util/LinkManager'; import { SerializationHelper } from '../client/util/SerializationHelper'; import { UndoManager } from '../client/util/UndoManager'; import { returnZero } from '../Utils'; -import CursorField from './CursorField'; -import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc'; -import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols'; -import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols'; +import { aclLevel, Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc'; +import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols'; +import { FieldChanged, Id, Parent, ToValue } from './FieldSymbols'; import { List } from './List'; import { ObjectField } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; @@ -51,11 +49,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number throw new Error("Can't put the same object in multiple documents at the same time"); } value[Parent] = receiver; - value[OnUpdate] = updateFunction(target, prop, value, receiver); + value[FieldChanged] = containedFieldChangedHandler(receiver, prop, value); } if (curValue instanceof ObjectField) { delete curValue[Parent]; - delete curValue[OnUpdate]; + delete curValue[FieldChanged]; } const effectiveAcl = GetEffectiveAcl(target); @@ -81,8 +79,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number } if (writeToServer) { - if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } }); - else target[Update]({ $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : value === undefined ? null : value } }); + // prettier-ignore + if (value === undefined) + (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $unset: { ['fields.' + prop]: '' } }); + else (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) :value}}); if (prop === 'author' || prop.toString().startsWith('acl')) updateCachedAcls(target); } else { DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); @@ -229,7 +229,8 @@ function getEffectiveAcl(target: any, user?: string): symbol { * @param key the key storing the access right (e.g. acl-groupname) * @param acl the access right being stored (e.g. "Can Edit") * @param target the document on which this access right is being set - * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection) + * @param visited list of Doc's already distributed to. + * @param allowUpgrade whether permissions can be made less restrictive * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change */ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean) { @@ -274,6 +275,9 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc dataDocChanged && updateCachedAcls(dataDoc); } +// +// target should be either a Doc or ListImpl. receiver should be a Proxy<Doc> Or List. +// export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop); @@ -344,86 +348,104 @@ export function deleteProperty(target: any, prop: string | number | symbol) { return true; } -export function updateFunction(target: any, prop: any, value: any, receiver: any) { - let lastValue = ObjectField.MakeCopy(value); - return (diff?: any) => { - const op = - diff?.op === '$addToSet' - ? { $addToSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } - : diff?.op === '$remFromSet' - ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)), hint: diff.hint } } - : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(value) } }; - !op.$set && ((op as any).length = diff.length); - const prevValue = ObjectField.MakeCopy(lastValue as List<any>); - lastValue = ObjectField.MakeCopy(value); - const newValue = ObjectField.MakeCopy(value); - - if (!(value instanceof CursorField) && !value?.some?.((v: any) => v instanceof CursorField)) { - !receiver[UpdatingFromServer] && +// this function creates a function that can be used to setup Undo for whenever an ObjectField changes. +// the idea is that the Doc field setter can only setup undo at the granularity of an entire field and won't even be called if +// just a part of a field (eg. field within an ObjectField) changes. This function returns a function that can be called +// whenever an internal ObjectField field changes. It should be passed a 'diff' specification describing the change. Currently, +// List's are the only true ObjectFields that can be partially modified (ignoring SchemaHeaderFields which should go away). +// The 'diff' specification that a list can send is limited to indicating that something was added, removed, or that the list contents +// were replaced. Based on this specification, an Undo event is setup that will save enough information about the ObjectField to be +// able to undo and redo the partial change. +// +export function containedFieldChangedHandler(container: List<Field> | Doc, prop: string | number, liveContainedField: ObjectField) { + let lastValue: FieldResult = liveContainedField instanceof ObjectField ? ObjectField.MakeCopy(liveContainedField) : liveContainedField; + return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => { + const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: Field) => SerializationHelper.Serialize(item)) }); + // prettier-ignore + const serverOp = diff?.op === '$addToSet' + ? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length } + : diff?.op === '$remFromSet' + ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint}, length: diff.length } + : { $set: { ['fields.' + prop]: liveContainedField ? SerializationHelper.Serialize(liveContainedField) : undefined } }; + + if (!(container instanceof Doc) || !container[UpdatingFromServer]) { + const prevValue = ObjectField.MakeCopy(lastValue as List<any>); + lastValue = ObjectField.MakeCopy(liveContainedField); + const newValue = ObjectField.MakeCopy(liveContainedField); + if (diff?.op === '$addToSet') { UndoManager.AddEvent( - diff?.op === '$addToSet' - ? { - redo: () => { - console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo - receiver[prop].push(...diff.items.map((item: any) => item.value ?? item)); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }, - undo: action(() => { - // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo - diff.items.forEach((item: any) => { - if (item instanceof SchemaHeaderField) { - const ind = receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); - ind !== -1 && receiver[prop].splice(ind, 1); - } else { - const ind = receiver[prop].indexOf(item.value ?? item); - ind !== -1 && receiver[prop].splice(ind, 1); - } - }); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }), - prop: 'add ' + diff.items.length + ' items to list', - } - : diff?.op === '$remFromSet' - ? { - redo: action(() => { - console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo - diff.items.forEach((item: any) => { - const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ?? item); - ind !== -1 && receiver[prop].splice(ind, 1); - }); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }), - undo: () => { - // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo - diff.items.forEach((item: any) => { - if (item instanceof SchemaHeaderField) { - const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); - ind !== -1 && receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && receiver[prop].splice(ind, 0, item); - } else { - const ind = (prevValue as List<any>).indexOf(item.value ?? item); - ind !== -1 && receiver[prop].indexOf(item.value ?? item) === -1 && receiver[prop].splice(ind, 0, item); - } - }); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }, - prop: 'remove ' + diff.items.length + ' items from list', - } - : { - redo: () => { - console.log('redo list: ' + prop, receiver[prop]); // bcz: uncomment to log undo - receiver[prop] = ObjectField.MakeCopy(newValue as List<any>); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }, - undo: () => { - // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo - receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>); - lastValue = ObjectField.MakeCopy(receiver[prop]); - }, - prop: 'assign list', - }, + { + redo: () => { + //console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo + (container as any)[prop as any]?.push(...(diff.items || [])?.map((item: any) => item.value ?? item)); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }, + undo: action(() => { + // console.log('undo $add: ' + prop, diff.items); // bcz: uncomment to log undo + diff.items?.forEach((item: any) => { + const ind = + item instanceof SchemaHeaderField // + ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) + : (container as any)[prop as any]?.indexOf(item.value ?? item); + ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1); + }); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }), + prop: 'add ' + diff.items?.length + ' items to list', + }, + diff?.items + ); + } else if (diff?.op === '$remFromSet') { + UndoManager.AddEvent( + { + redo: action(() => { + // console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo + diff.items?.forEach((item: any) => { + const ind = + item instanceof SchemaHeaderField // + ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) + : (container as any)[prop as any]?.indexOf(item.value ?? item); + ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1); + }); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }), + undo: () => { + // console.log('undo $rem: ' + prop, diff.items); // bcz: uncomment to log undo + diff.items?.forEach((item: any) => { + if (item instanceof SchemaHeaderField) { + const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading); + ind !== -1 && (container as any)[prop as any].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && (container as any)[prop as any].splice(ind, 0, item); + } else { + const ind = (prevValue as List<any>).indexOf(item.value ?? item); + ind !== -1 && (container as any)[prop as any].indexOf(item.value ?? item) === -1 && (container as any)[prop as any].splice(ind, 0, item); + } + }); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }, + prop: 'remove ' + diff.items?.length + ' items from list', + }, diff?.items ); + } else { + const setFieldVal = (val: Field | undefined) => (container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as Field)); + UndoManager.AddEvent( + { + redo: () => { + // console.log('redo list: ' + prop, fieldVal()); // bcz: uncomment to log undo + setFieldVal(newValue instanceof ObjectField ? ObjectField.MakeCopy(newValue) : undefined); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }, + undo: () => { + // console.log('undo list: ' + prop, fieldVal()); // bcz: uncomment to log undo + setFieldVal(prevValue instanceof ObjectField ? ObjectField.MakeCopy(prevValue) : undefined); + lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + }, + prop: 'set list field', + }, + diff?.items + ); + } } - target[Update](op); + container[FieldChanged]?.(undefined, serverOp); }; } |
