diff options
author | eleanor-park <eleanor_park@brown.edu> | 2024-08-27 16:44:12 -0400 |
---|---|---|
committer | eleanor-park <eleanor_park@brown.edu> | 2024-08-27 16:44:12 -0400 |
commit | 39d2bba7bf4b0cc3759931691640083a48cce662 (patch) | |
tree | 8bf110760aa926237b6294aec545f48cfc92747d /src/fields/util.ts | |
parent | 6f73686ec4dc3e01ae3eacc0150aa59eafea0325 (diff) | |
parent | b8a04a0fedf8ef3612395764a0ecd01f6824ebd1 (diff) |
Merge branch 'master' into eleanor-gptdraw
Diffstat (limited to 'src/fields/util.ts')
-rw-r--r-- | src/fields/util.ts | 148 |
1 files changed, 85 insertions, 63 deletions
diff --git a/src/fields/util.ts b/src/fields/util.ts index a6499c3e3..60eadcdfd 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -7,8 +7,8 @@ import { UndoManager } from '../client/util/UndoManager'; import { Doc, DocListCast, FieldType, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, aclLevel, updateCachedAcls } from './Doc'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, DirectLinks, 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 { List, ListImpl } from './List'; +import { ObjectField, serializedFieldType, serverOpType } from './ObjectField'; import { PrefetchProxy, ProxyField } from './Proxy'; import { RefField } from './RefField'; import { RichTextField } from './RichTextField'; @@ -44,15 +44,23 @@ export function TraceMobx() { tracing && trace(); } -export const _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>(); +export const _propSetterCB = new Map<string, ((target: Doc, value: FieldType) => void) | undefined>(); -const _setterImpl = action((target: any, prop: string | symbol | number, valueIn: any, receiver: any): boolean => { +const _setterImpl = action((target: Doc | ListImpl<FieldType>, prop: string | symbol | number, valueIn: unknown, receiver: Doc | ListImpl<FieldType>): boolean => { + if (target instanceof ListImpl) { + if (typeof prop !== 'symbol' && +prop == prop) { + target[SelfProxy].splice(+prop, 1, valueIn as FieldType); + } else { + target[prop] = valueIn as FieldType; + } + return true; + } if (SerializationHelper.IsSerializing() || typeof prop === 'symbol') { - target[prop] = valueIn; + target[prop] = valueIn as FieldResult; return true; } - let value = valueIn?.[SelfProxy] ?? valueIn; // convert any Doc type values to Proxy's + let value = (valueIn as Doc | ListImpl<FieldType>)?.[SelfProxy] ?? valueIn; // convert any Doc type values to Proxy's const curValue = target.__fieldTuples[prop]; if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) { @@ -60,7 +68,7 @@ const _setterImpl = action((target: any, prop: string | symbol | number, valueIn // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way return true; } - if (value instanceof RefField) { + if (value instanceof Doc) { value = new ProxyField(value); } @@ -77,7 +85,7 @@ const _setterImpl = action((target: any, prop: string | symbol | number, valueIn delete curValue[FieldChanged]; } - if (typeof prop === 'string' && _propSetterCB.has(prop)) _propSetterCB.get(prop)!(target[SelfProxy], value); + if (typeof prop === 'string' && _propSetterCB.has(prop)) _propSetterCB.get(prop)!(target[SelfProxy], value as FieldType); // eslint-disable-next-line no-use-before-define const effectiveAcl = GetEffectiveAcl(target); @@ -104,20 +112,21 @@ const _setterImpl = action((target: any, prop: string | symbol | number, valueIn if (writeToServer) { // prettier-ignore - if (value === undefined) + if (value === undefined || value === null) (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}}); + else (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $set: { ['fields.' + prop]: (value instanceof ObjectField ? SerializationHelper.Serialize(value) :value) as { fields: serializedFieldType[]}}}); if (prop === 'author' || prop.toString().startsWith('acl_')) updateCachedAcls(target); - } else { + } else if (receiver instanceof Doc) { DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); } !receiver[Initializing] && + receiver instanceof Doc && !StrListCast(receiver.undoIgnoreFields).includes(prop.toString()) && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent( { redo: () => { - receiver[prop] = value; + receiver[prop] = value as FieldType; }, undo: () => { const wasUpdate = receiver[UpdatingFromServer]; @@ -137,7 +146,7 @@ const _setterImpl = action((target: any, prop: string | symbol | number, valueIn return true; }); -let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl; +let _setter: (target: Doc | ListImpl<FieldType>, prop: string | symbol | number, value: FieldType | undefined, receiver: Doc | ListImpl<FieldType>) => boolean = _setterImpl; export function makeReadOnly() { _setter = _readOnlySetter; @@ -156,18 +165,18 @@ export function denormalizeEmail(email: string) { // return acl from cache or cache the acl and return. // eslint-disable-next-line no-use-before-define -const getEffectiveAclCache = computedFn((target: any, user?: string) => getEffectiveAcl(target, user), true); +const getEffectiveAclCache = computedFn((target: Doc | ListImpl<FieldType>, user?: string) => getEffectiveAcl(target, user), true); /** * Calculates the effective access right to a document for the current user. */ -export function GetEffectiveAcl(target: any, user?: string): symbol { +export function GetEffectiveAcl(target: Doc | ListImpl<FieldType>, user?: string): symbol { if (!target) return AclPrivate; if (target[UpdatingFromServer] || ClientUtils.CurrentUserEmail() === 'guest') return AclAdmin; 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) } -export function GetPropAcl(target: any, prop: string | symbol | number) { +export function GetPropAcl(target: Doc | ListImpl<FieldType>, prop: string | symbol | number) { 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); @@ -182,7 +191,8 @@ export function GetCachedGroupByName(name: string) { export function SetCachedGroups(groups: string[]) { runInAction(() => cachedGroups.push(...groups)); } -function getEffectiveAcl(target: any, user?: string): symbol { +function getEffectiveAcl(target: Doc | ListImpl<FieldType>, user?: string): symbol { + if (target instanceof ListImpl) return AclAdmin; const targetAcls = target[DocAcl]; if (targetAcls?.acl_Me === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin; @@ -287,14 +297,14 @@ export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) * @param prop * @param propSetter */ -export function SetPropSetterCb(prop: string, propSetter: ((target: any, value: any) => void) | undefined) { +export function SetPropSetterCb(prop: string, propSetter: ((target: Doc, value: FieldType) => void) | undefined) { _propSetterCB.set(prop, propSetter); } // // target should be either a Doc or ListImpl. receiver should be a Proxy<Doc> Or List. // -export function setter(target: any, inProp: string | symbol | number, value: any, receiver: any): boolean { +export function setter(target: ListImpl<FieldType> | Doc, inProp: string | symbol | number, value: unknown, receiver: Doc | ListImpl<FieldType>): boolean { if (!inProp) { console.log('WARNING: trying to set an empty property. This should be fixed. '); return false; @@ -303,12 +313,12 @@ export function setter(target: any, inProp: string | symbol | number, value: any const effectiveAcl = inProp === 'constructor' || typeof inProp === 'symbol' ? AclAdmin : GetPropAcl(target, prop); if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && 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 - if (typeof prop === 'string' && prop.startsWith('acl_') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; + if (typeof prop === 'string' && prop.startsWith('acl_') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value as SharingPermissions))) return true; if (typeof prop === 'string' && prop !== '__id' && prop !== '__fieldTuples' && prop.startsWith('_')) { if (!prop.startsWith('__')) prop = prop.substring(1); - if (target.__LAYOUT__) { - target.__LAYOUT__[prop] = value; + if (target.__LAYOUT__ instanceof Doc) { + target.__LAYOUT__[prop] = value as FieldResult; return true; } } @@ -317,10 +327,10 @@ export function setter(target: any, inProp: string | symbol | number, value: any return !!ScriptCast(target.__fieldTuples[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success; } } - return _setter(target, prop, value, receiver); + return _setter(target, prop, value as FieldType, receiver); } -function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProto: boolean = false): any { +function getFieldImpl(target: ListImpl<FieldType> | Doc, prop: string | number, proxy: ListImpl<FieldType> | Doc, ignoreProto: boolean = false): FieldType { const field = target.__fieldTuples[prop]; const value = field?.[ToValue]?.(proxy); // converts ComputedFields to values, or unpacks ProxyFields into Proxys if (value) return value.value; @@ -332,7 +342,7 @@ function getFieldImpl(target: any, prop: string | number, proxy: any, ignoreProt } return field; } -export function getter(target: any, prop: string | symbol, proxy: any): any { +export function getter(target: Doc | ListImpl<FieldType>, prop: string | symbol, proxy: ListImpl<FieldType> | Doc): unknown { // prettier-ignore switch (prop) { case 'then' : return undefined; @@ -352,19 +362,23 @@ export function getter(target: any, prop: string | symbol, proxy: any): any { } const layoutProp = prop.startsWith('_') ? prop.substring(1) : undefined; - if (layoutProp && target.__LAYOUT__) return target.__LAYOUT__[layoutProp]; + if (layoutProp && target.__LAYOUT__) return (target.__LAYOUT__ as Doc)[layoutProp]; return getFieldImpl(target, layoutProp ?? prop, proxy); } -export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any { - return getFieldImpl(target, prop, target[SelfProxy], ignoreProto); +export function getField(target: ListImpl<FieldType> | Doc, prop: string | number, ignoreProto: boolean = false): unknown { + return getFieldImpl(target, prop, target[SelfProxy] as Doc, ignoreProto); } -export function deleteProperty(target: any, prop: string | number | symbol) { +export function deleteProperty(target: Doc | ListImpl<FieldType>, prop: string | number | symbol) { if (typeof prop === 'symbol') { delete target[prop]; } else { - target[SelfProxy][prop] = undefined; + if (target instanceof Doc) { + target[SelfProxy][prop] = undefined; + } else if (+prop == prop) { + target[SelfProxy].splice(+prop, 1); + } } return true; } @@ -378,39 +392,42 @@ export function deleteProperty(target: any, prop: string | number | symbol) { // 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<FieldType> | Doc, prop: string | number, liveContainedField: ObjectField) { - let lastValue: FieldResult = liveContainedField instanceof ObjectField ? ObjectField.MakeCopy(liveContainedField) : liveContainedField; - return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any } /* , dummyServerOp?: any */) => { - const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: FieldType) => SerializationHelper.Serialize(item)) }); +export function containedFieldChangedHandler(container: ListImpl<FieldType> | Doc, prop: string | number, liveContainedField: ObjectField) { + let lastValue = ObjectField.MakeCopy(liveContainedField); + return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: (FieldType & { value?: FieldType })[] | undefined; length: number | undefined; hint?: { start: number; deleteCount: number } } /* , dummyServerOp?: any */) => { + const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: FieldType) => SerializationHelper.Serialize(item) as serializedFieldType) ?? [] }); // prettier-ignore - const serverOp = diff?.op === '$addToSet' - ? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length } + const serverOp: serverOpType = diff?.op === '$addToSet' + ? { $addToSet: { ['fields.' + prop]: serializeItems(), length: diff.length ??0 }} : diff?.op === '$remFromSet' - ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint}, length: diff.length } - : { $set: { ['fields.' + prop]: liveContainedField ? SerializationHelper.Serialize(liveContainedField) : undefined } }; + ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint, length: diff.length ?? 0 } } + : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(liveContainedField) as {fields: serializedFieldType[]}} }; if (!(container instanceof Doc) || !container[UpdatingFromServer]) { - const prevValue = ObjectField.MakeCopy(lastValue as List<any>); + const cont = container as { [key: string | number]: FieldType }; + const prevValue = ObjectField.MakeCopy(lastValue as List<FieldType>); lastValue = ObjectField.MakeCopy(liveContainedField); const newValue = ObjectField.MakeCopy(liveContainedField); if (diff?.op === '$addToSet') { UndoManager.AddEvent( { redo: () => { + const contList = cont[prop] as List<FieldType>; // 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]); + contList?.push(...((diff.items || [])?.map(item => item.value ?? item) ?? [])); + lastValue = ObjectField.MakeCopy(contList); }, undo: action(() => { + const contList = cont[prop] as List<FieldType>; // console.log('undo $add: ' + prop, diff.items); // bcz: uncomment to log undo - diff.items?.forEach((item: any) => { + diff.items?.forEach(item => { 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); + ? contList?.findIndex(ele => ele instanceof SchemaHeaderField && ele.heading === item.heading) + : contList?.indexOf(item.value ?? item); + ind !== undefined && ind !== -1 && (cont[prop] as List<FieldType>)?.splice(ind, 1); }); - lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + lastValue = ObjectField.MakeCopy(contList); }), prop: 'add ' + (diff.items?.length ?? 0) + ' items to list', }, @@ -420,48 +437,53 @@ export function containedFieldChangedHandler(container: List<FieldType> | Doc, p UndoManager.AddEvent( { redo: action(() => { + const contList = cont[prop] as List<FieldType>; // console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo - diff.items?.forEach((item: any) => { + diff.items?.forEach(item => { 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); + ? contList?.findIndex(ele => ele instanceof SchemaHeaderField && ele.heading === item.heading) + : contList?.indexOf(item.value ?? item); + ind !== undefined && ind !== -1 && contList?.splice(ind, 1); }); - lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + lastValue = ObjectField.MakeCopy(contList); }), undo: () => { + const contList = cont[prop] as List<FieldType>; + const prevList = prevValue as List<FieldType>; // console.log('undo $rem: ' + prop, diff.items); // bcz: uncomment to log undo - diff.items?.forEach((item: any) => { + diff.items?.forEach(item => { 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); + const ind = prevList.findIndex(ele => ele instanceof SchemaHeaderField && ele.heading === item.heading); + ind !== -1 && contList.findIndex(ele => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && contList.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); + const ind = prevList.indexOf(item.value ?? item); + ind !== -1 && contList.indexOf(item.value ?? item) === -1 && (cont[prop] as List<FieldType>).splice(ind, 0, item); } }); - lastValue = ObjectField.MakeCopy((container as any)[prop as any]); + lastValue = ObjectField.MakeCopy(contList); }, - prop: 'remove ' + (diff.items?.length ?? 0) + ' items from list(' + ((container as any)?.title ?? '') + ':' + prop + ')', + prop: 'remove ' + (diff.items?.length ?? 0) + ' items from list(' + (cont?.title ?? '') + ':' + prop + ')', }, diff?.items ); } else { const setFieldVal = (val: FieldType | undefined) => { - container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as FieldType); + container instanceof Doc ? (container[prop] = val) : (container[prop as number] = val as FieldType); }; 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]); + setFieldVal(ObjectField.MakeCopy(newValue)); + const containerProp = cont[prop]; + if (containerProp instanceof ObjectField) lastValue = ObjectField.MakeCopy(containerProp); }, 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]); + setFieldVal(ObjectField.MakeCopy(prevValue)); + const containerProp = cont[prop]; + if (containerProp instanceof ObjectField) lastValue = ObjectField.MakeCopy(containerProp); }, prop: 'set list field', }, |