diff options
Diffstat (limited to 'src/fields/util.ts')
-rw-r--r-- | src/fields/util.ts | 299 |
1 files changed, 115 insertions, 184 deletions
diff --git a/src/fields/util.ts b/src/fields/util.ts index 4a62a6a1f..e439768ee 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,42 +1,22 @@ -import { action, observable, runInAction, trace } from 'mobx'; +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 { CollectionDockingView } from '../client/views/collections/CollectionDockingView'; import { returnZero } from '../Utils'; import CursorField from './CursorField'; -import { - AclAdmin, - AclAugment, - AclEdit, - AclPrivate, - AclReadonly, - AclSelfEdit, - AclSym, - AclUnset, - DataSym, - Doc, - DocListCast, - DocListCastAsync, - FieldResult, - ForceServerWrite, - HeightSym, - Initializing, - LayoutSym, - updateCachedAcls, - UpdatingFromServer, - WidthSym, -} from './Doc'; -import { Id, OnUpdate, Parent, Self, SelfProxy, Update } from './FieldSymbols'; +import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc'; +import { AclAdmin, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols'; +import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols'; import { List } from './List'; import { ObjectField } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; import { RefField } from './RefField'; import { RichTextField } from './RichTextField'; import { SchemaHeaderField } from './SchemaHeaderField'; -import { ComputedField } from './ScriptField'; +import { ComputedField, ScriptField } from './ScriptField'; import { ScriptCast, StrCast } from './Types'; function _readOnlySetter(): never { @@ -48,33 +28,16 @@ export function TraceMobx() { tracing && trace(); } -export interface GetterResult { - value: FieldResult; - shouldReturn?: boolean; -} -export type GetterPlugin = (receiver: any, prop: string | number, currentValue: any) => GetterResult | undefined; -const getterPlugins: GetterPlugin[] = []; - -export namespace Plugins { - export function addGetterPlugin(plugin: GetterPlugin) { - getterPlugins.push(plugin); - } -} - const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { - if (SerializationHelper.IsSerializing()) { + if (SerializationHelper.IsSerializing() || typeof prop === 'symbol') { target[prop] = value; return true; } - if (typeof prop === 'symbol') { - target[prop] = value; - return true; - } if (value !== undefined) { value = value[SelfProxy] || value; } - const curValue = target.__fields[prop]; + const curValue = target.__fieldTuples[prop]; if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) { // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way @@ -83,6 +46,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (value instanceof RefField) { value = new ProxyField(value); } + if (value instanceof ObjectField) { if (value[Parent] && value[Parent] !== receiver && !(value instanceof PrefetchProxy)) { throw new Error("Can't put the same object in multiple documents at the same time"); @@ -105,30 +69,41 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (writeToDoc) { if (value === undefined) { - target.__fieldKeys && delete target.__fieldKeys[prop]; - delete target.__fields[prop]; + target[FieldKeys] && delete target[FieldKeys][prop]; // Lists don't have a FieldKeys field + delete target.__fieldTuples[prop]; } else { - target.__fieldKeys && (target.__fieldKeys[prop] = true); - // if (target.__fields[prop] !== value) { - // console.log("ASSIGN " + prop + " " + value); - // } - target.__fields[prop] = value; + // bcz: uncomment to see if server is being updated + // console.log(prop + ' = ' + value + '(' + curValue + ')'); + target[FieldKeys] && (target[FieldKeys][prop] = true); // Lists don't have a FieldKeys field + target.__fieldTuples[prop] = value; } - //if (typeof value === "object" && !(value instanceof ObjectField)) debugger; 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 } }); + if (prop === 'author' || prop.toString().startsWith('acl')) updateCachedAcls(target); } else { DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); } !receiver[Initializing] && + !StrListCast(receiver.undoIgnoreFields).includes(prop.toString()) && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && - UndoManager.AddEvent({ - redo: () => (receiver[prop] = value), - undo: () => (receiver[prop] = curValue), - prop: prop?.toString(), - }); + UndoManager.AddEvent( + { + redo: () => (receiver[prop] = value), + undo: () => { + const wasUpdate = receiver[UpdatingFromServer]; + const wasForce = receiver[ForceServerWrite]; + receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet + receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable. + receiver[prop] = curValue; + receiver[ForceServerWrite] = wasForce; + receiver[UpdatingFromServer] = wasUpdate; + }, + prop: prop?.toString(), + }, + value + ); return true; } return false; @@ -151,24 +126,17 @@ export function denormalizeEmail(email: string) { return email.replace(/__/g, '.'); } -// playground mode allows the user to add/delete documents or make layout changes without them saving to the server -// let playgroundMode = false; - -// export function togglePlaygroundMode() { -// playgroundMode = !playgroundMode; -// } - /** * Copies parent's acl fields to the child */ export function inheritParentAcls(parent: Doc, child: Doc) { return; - const dataDoc = parent[DataSym]; - for (const key of Object.keys(dataDoc)) { - // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. - const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key]; - key.startsWith('acl') && distributeAcls(key, permission, child); - } + // const dataDoc = parent[DataSym]; + // for (const key of Object.keys(dataDoc)) { + // // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. + // const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key]; + // key.startsWith('acl') && distributeAcls(key, permission, child); + // } } /** @@ -183,8 +151,11 @@ export function inheritParentAcls(parent: Doc, child: Doc) { * View: a user with view access to a document can only view it - they cannot add/remove/edit anything. * * None: the document is not shared with that user. + * + * Unset: Remove a sharing permission (eg., used ) */ export enum SharingPermissions { + Unset = 'None', Admin = 'Admin', Edit = 'Edit', SelfEdit = 'Self Edit', @@ -204,22 +175,16 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { export function GetEffectiveAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; if (target[UpdatingFromServer]) return AclAdmin; - // authored documents are private until an ACL is set. - if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate; return 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 || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent + if (typeof prop === 'symbol' || target[UpdatingFromServer]) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable return GetEffectiveAcl(target); } -let HierarchyMapping: Map<symbol, number> | undefined; - let cachedGroups = observable([] as string[]); -/// 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. const getCachedGroupByNameCache = computedFn(function (name: string) { return cachedGroups.includes(name); }, true); @@ -230,43 +195,33 @@ export function SetCachedGroups(groups: string[]) { runInAction(() => cachedGroups.push(...groups)); } function getEffectiveAcl(target: any, user?: string): symbol { - 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 - const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author - if (userChecked === targetAuthor || !targetAuthor) return AclAdmin; - if (GetCachedGroupByName('Admin')) return AclAdmin; + const targetAcls = target[DocAcl]; + if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin; + 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 (targetAcls && Object.keys(targetAcls).length) { - HierarchyMapping = - HierarchyMapping || - new Map<symbol, number>([ - [AclPrivate, 0], - [AclReadonly, 1], - [AclAugment, 2], - [AclSelfEdit, 2.5], - [AclEdit, 3], - [AclAdmin, 4], - ]); - 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 (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { - if (GetCachedGroupByName(entity) || userChecked === entity) { + if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) { + if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') { effectiveAcl = value as symbol; } } } // 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 = targetAcls['acl-Override']; - if (override !== AclUnset && override !== undefined) effectiveAcl = 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; + return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl; } + // authored documents are private until an ACL is set. + const targetAuthor = target.__fieldTuples?.author || target.author; // target may be a Doc of Proxy, so check __fieldTuples.author and .author + if (targetAuthor && targetAuthor !== userChecked) return AclPrivate; return AclAdmin; } /** @@ -279,9 +234,9 @@ function getEffectiveAcl(target: any, user?: string): symbol { */ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[], isDashboard?: boolean) { if (!visited) visited = [] as Doc[]; - if (visited.includes(target)) return; + if (!target || visited.includes(target)) return; - if ((target._viewType === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) { + if ((target._type_collection === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) { target[key] = acl; if (target !== Doc.GetProto(target)) { //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???) @@ -291,21 +246,9 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc } visited.push(target); - const HierarchyMapping = new Map<string, number>([ - ['Not Shared', 0], - ['Can View', 1], - ['Can Augment', 2], - ['Self Edit', 2.5], - ['Can Edit', 3], - ['Admin', 4], - ]); - let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) - let dataDocChanged = false; - const dataDoc = target[DataSym]; - // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive - if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!)) { + if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { target[key] = acl; layoutDocChanged = true; @@ -316,34 +259,27 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc } } - if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) { + let dataDocChanged = false; + const dataDoc = target[DocData]; + if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) { if (GetEffectiveAcl(dataDoc) === AclAdmin) { dataDoc[key] = acl; dataDocChanged = true; } // maps over the links of the document - const links = DocListCast(dataDoc.links); - links.forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited)); + LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited)); // maps over the children of the document - DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? '-all' : '')]).map(d => { + DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => { distributeAcls(key, acl, d, inheritingFromCollection, visited); - // } - const data = d[DataSym]; - if (data) { - distributeAcls(key, acl, data, inheritingFromCollection, visited); - } + distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited); }); // maps over the annotations of the document - DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '-annotations']).map(d => { + DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => { distributeAcls(key, acl, d, inheritingFromCollection, visited); - // } - const data = d[DataSym]; - if (data) { - distributeAcls(key, acl, data, inheritingFromCollection, visited); - } + distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited); }); } @@ -353,80 +289,71 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; - const effectiveAcl = getPropAcl(target, prop); + const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : getPropAcl(target, prop); if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) 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 - 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 Augment", "Can View", "Not Shared", undefined].includes(value)) return true; + if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; - if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) { + if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_')) { if (!prop.startsWith('__')) prop = prop.substring(1); if (target.__LAYOUT__) { target.__LAYOUT__[prop] = value; return true; } } - if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) { - return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false; + if (target.__fieldTuples[prop] instanceof ComputedField) { + if (target.__fieldTuples[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) { + return ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false; + } } return _setter(target, prop, value, receiver); } -export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { - let prop = in_prop; - - 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) return prop === HeightSym || prop === WidthSym ? returnZero : undefined; - if (prop === LayoutSym) return target.__LAYOUT__; - if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) { - if (!prop.startsWith('__')) prop = prop.substring(1); - if (target.__LAYOUT__) return target.__LAYOUT__[prop]; +export function getter(target: any, prop: string | symbol, proxy: any): any { + // prettier-ignore + switch (prop) { + case 'then' : return undefined; + case 'constructor': case 'toString': case 'valueOf': + case 'serializeInfo': case 'factory': + return target[prop]; + case DocAcl : return target[DocAcl]; + case $mobx: return target.__fieldTuples[prop]; + case DocLayout: return target.__LAYOUT__; + case Height: case Width: if (GetEffectiveAcl(target) === AclPrivate) return returnZero; + default : + if (typeof prop === 'symbol') return target[prop]; + if (prop.startsWith('isMobX')) return target[prop]; + if (prop.startsWith('__')) return target[prop]; + if (GetEffectiveAcl(target) === AclPrivate && prop !== 'author') return undefined; } - if (prop === 'then') { - //If we're being awaited - return undefined; - } - if (typeof prop === 'symbol') { - return target.__fields[prop] || target[prop]; - } - if (SerializationHelper.IsSerializing()) { - return target[prop]; - } - return getFieldImpl(target, prop, receiver); + + const layout_prop = prop.startsWith('_') ? prop.substring(1) : undefined; + if (layout_prop && target.__LAYOUT__) return target.__LAYOUT__[layout_prop]; + return getFieldImpl(target, layout_prop ?? prop, proxy); } -function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any { - receiver = receiver || target[SelfProxy]; - let field = target.__fields[prop]; - for (const plugin of getterPlugins) { - const res = plugin(receiver, prop, field); - if (res === undefined) continue; - if (res.shouldReturn) { - return res.value; - } else { - field = res.value; - } - } +function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any { + const field = target.__fieldTuples[prop]; + const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys + if (value) return value.value; if (field === undefined && !ignoreProto && prop !== 'proto') { - const proto = getFieldImpl(target, 'proto', receiver, true); //TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters + const proto = getFieldImpl(target, 'proto', proxy, true); //TODO tfs: instead of proxy we could use target[SelfProxy]... I don't which semantics we want or if it really matters if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) { - return getFieldImpl(proto[Self], prop, receiver, ignoreProto); + return getFieldImpl(proto, prop, proxy, ignoreProto); } - return undefined; } return field; } export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any { - return getFieldImpl(target, prop, undefined, ignoreProto); + return getFieldImpl(target, prop, target[SelfProxy], ignoreProto); } export function deleteProperty(target: any, prop: string | number | symbol) { if (typeof prop === 'symbol') { delete target[prop]; - return true; + } else { + target[SelfProxy][prop] = undefined; } - target[SelfProxy][prop] = undefined; return true; } @@ -437,7 +364,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any 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)) } } + ? { $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>); @@ -450,7 +377,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any diff?.op === '$addToSet' ? { redo: () => { - receiver[prop].push(...diff.items.map((item: any) => (item.value ? item.value() : item))); + 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(() => { @@ -460,19 +388,20 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any 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.value() : item); + const ind = receiver[prop].indexOf(item.value ?? item); ind !== -1 && receiver[prop].splice(ind, 1); } }); lastValue = ObjectField.MakeCopy(receiver[prop]); }), - 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.value() : item); + 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]); @@ -484,16 +413,17 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any 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.value() : item); - ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item); + 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: '', + 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]); }, @@ -502,8 +432,9 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>); lastValue = ObjectField.MakeCopy(receiver[prop]); }, - prop: '', - } + prop: 'assign list', + }, + diff?.items ); } target[Update](op); |