aboutsummaryrefslogtreecommitdiff
path: root/src/fields/util.ts
diff options
context:
space:
mode:
authoreleanor-park <eleanor_park@brown.edu>2024-08-27 16:44:12 -0400
committereleanor-park <eleanor_park@brown.edu>2024-08-27 16:44:12 -0400
commit39d2bba7bf4b0cc3759931691640083a48cce662 (patch)
tree8bf110760aa926237b6294aec545f48cfc92747d /src/fields/util.ts
parent6f73686ec4dc3e01ae3eacc0150aa59eafea0325 (diff)
parentb8a04a0fedf8ef3612395764a0ecd01f6824ebd1 (diff)
Merge branch 'master' into eleanor-gptdraw
Diffstat (limited to 'src/fields/util.ts')
-rw-r--r--src/fields/util.ts148
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',
},