aboutsummaryrefslogtreecommitdiff
path: root/src/fields/List.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/fields/List.ts')
-rw-r--r--src/fields/List.ts459
1 files changed, 216 insertions, 243 deletions
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 033fa569b..183d644d3 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -3,217 +3,15 @@ import { alias, list, serializable } from 'serializr';
import { DocServer } from '../client/DocServer';
import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { afterDocDeserialize, autoObject, Deserializable } from '../client/util/SerializationHelper';
-import { FieldTuples, Self, SelfProxy, Update } from './DocSymbols';
import { Field } from './Doc';
-import { Copy, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { FieldTuples, Self, SelfProxy } from './DocSymbols';
+import { Copy, FieldChanged, Parent, ToScriptString, ToString } from './FieldSymbols';
import { ObjectField } from './ObjectField';
import { ProxyField } from './Proxy';
import { RefField } from './RefField';
import { listSpec } from './Schema';
import { Cast } from './Types';
-import { deleteProperty, getter, setter, updateFunction } from './util';
-
-const listHandlers: any = {
- /// Mutator methods
- copyWithin() {
- throw new Error('copyWithin not supported yet');
- },
- fill(value: any, start?: number, end?: number) {
- if (value instanceof RefField) {
- throw new Error('fill with RefFields not supported yet');
- }
- const res = this[Self].__fieldTuples.fill(value, start, end);
- this[Update]();
- return res;
- },
- pop(): any {
- const field = toRealField(this[Self].__fieldTuples.pop());
- this[Update]();
- return field;
- },
- push: action(function (this: any, ...items: any[]) {
- items = items.map(toObjectField);
-
- const list = this[Self];
- const length = list.__fieldTuples.length;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i + length, item, this);
- }
- }
- const res = list.__fieldTuples.push(...items);
- this[Update]({ op: '$addToSet', items, length: length + items.length });
- return res;
- }),
- reverse() {
- const res = this[Self].__fieldTuples.reverse();
- this[Update]();
- return res;
- },
- shift() {
- const res = toRealField(this[Self].__fieldTuples.shift());
- this[Update]();
- return res;
- },
- sort(cmpFunc: any) {
- this[Self].__realFields(); // coerce retrieving entire array
- const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
- this[Update]();
- return res;
- },
- splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
- this[Self].__realFields(); // coerce retrieving entire array
- items = items.map(toObjectField);
- const list = this[Self];
- const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- //TODO Need to change indices of other fields in array
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i + start, item, this);
- }
- }
- let hintArray: { val: any; index: number }[] = [];
- for (let i = start; i < start + deleteCount; i++) {
- hintArray.push({ val: list.__fieldTuples[i], index: i });
- }
- const res = list.__fieldTuples.splice(start, deleteCount, ...items);
- // the hint object sends the starting index of the slice and the number
- // of elements to delete.
- this[Update](
- items.length === 0 && deleteCount
- ? { op: '$remFromSet', items: removed, hint: { start: start, deleteCount: deleteCount }, length: list.__fieldTuples.length }
- : items.length && !deleteCount && start === list.__fieldTuples.length
- ? { op: '$addToSet', items, length: list.__fieldTuples.length }
- : undefined
- );
- return res.map(toRealField);
- }),
- unshift(...items: any[]) {
- items = items.map(toObjectField);
- const list = this[Self];
- const length = list.__fieldTuples.length;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- //TODO Need to change indices of other fields in array
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i, item, this);
- }
- }
- const res = this[Self].__fieldTuples.unshift(...items);
- this[Update]();
- return res;
- },
- /// Accessor methods
- concat: action(function (this: any, ...items: any[]) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.map(toRealField).concat(...items);
- }),
- includes(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().includes(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
- }
- },
- indexOf(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().indexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
- }
- },
- join(separator: any) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.map(toRealField).join(separator);
- },
- lastElement() {
- return this[Self].__realFields().lastElement();
- },
- lastIndexOf(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
- }
- },
- slice(begin: number, end: number) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
- },
-
- /// Iteration methods
- entries() {
- return this[Self].__realFields().entries();
- },
- every(callback: any, thisArg: any) {
- return this[Self].__realFields().every(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- filter(callback: any, thisArg: any) {
- return this[Self].__realFields().filter(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- find(callback: any, thisArg: any) {
- return this[Self].__realFields().find(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- findIndex(callback: any, thisArg: any) {
- return this[Self].__realFields().findIndex(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- forEach(callback: any, thisArg: any) {
- return this[Self].__realFields().forEach(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- map(callback: any, thisArg: any) {
- return this[Self].__realFields().map(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- reduce(callback: any, initialValue: any) {
- return this[Self].__realFields().reduce(callback, initialValue);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
- },
- reduceRight(callback: any, initialValue: any) {
- return this[Self].__realFields().reduceRight(callback, initialValue);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
- },
- some(callback: any, thisArg: any) {
- return this[Self].__realFields().some(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- values() {
- return this[Self].__realFields().values();
- },
- [Symbol.iterator]() {
- return this[Self].__realFields().values();
- },
-};
+import { deleteProperty, getter, setter, containedFieldChangedHandler } from './util';
function toObjectField(field: Field) {
return field instanceof RefField ? new ProxyField(field) : field;
@@ -223,38 +21,221 @@ function toRealField(field: Field) {
return field instanceof ProxyField ? field.value : field;
}
-function listGetter(target: any, prop: string | symbol, receiver: any): any {
- if (listHandlers.hasOwnProperty(prop)) {
- return listHandlers[prop];
- }
- return getter(target, prop, receiver);
-}
-
-interface ListSpliceUpdate<T> {
- type: 'splice';
- index: number;
- added: T[];
- removedCount: number;
-}
-
-interface ListIndexUpdate<T> {
- type: 'update';
- index: number;
- newValue: T;
-}
-
-type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
-
type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
export const ListFieldName = 'fields';
@Deserializable('list')
class ListImpl<T extends Field> extends ObjectField {
+ static listHandlers: any = {
+ /// Mutator methods
+ copyWithin() {
+ throw new Error('copyWithin not supported yet');
+ },
+ fill(value: any, start?: number, end?: number) {
+ if (value instanceof RefField) {
+ throw new Error('fill with RefFields not supported yet');
+ }
+ const res = this[Self].__fieldTuples.fill(value, start, end);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ pop(): any {
+ const field = toRealField(this[Self].__fieldTuples.pop());
+ this[SelfProxy][FieldChanged]?.();
+ return field;
+ },
+ push: action(function (this: ListImpl<any>, ...items: any[]) {
+ items = items.map(toObjectField);
+
+ const list = this[Self];
+ const length = list.__fieldTuples.length;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], i + length, item);
+ }
+ }
+ const res = list.__fieldTuples.push(...items);
+ this[SelfProxy][FieldChanged]?.({ op: '$addToSet', items, length: length + items.length });
+ return res;
+ }),
+ reverse() {
+ const res = this[Self].__fieldTuples.reverse();
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ shift() {
+ const res = toRealField(this[Self].__fieldTuples.shift());
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ sort(cmpFunc: any) {
+ this[Self].__realFields(); // coerce retrieving entire array
+ const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+ this[Self].__realFields(); // coerce retrieving entire array
+ items = items.map(toObjectField);
+ const list = this[Self];
+ const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this, i + start, item);
+ }
+ }
+ let hintArray: { val: any; index: number }[] = [];
+ for (let i = start; i < start + deleteCount; i++) {
+ hintArray.push({ val: list.__fieldTuples[i], index: i });
+ }
+ const res = list.__fieldTuples.splice(start, deleteCount, ...items);
+ // the hint object sends the starting index of the slice and the number
+ // of elements to delete.
+ this[SelfProxy][FieldChanged]?.(
+ items.length === 0 && deleteCount
+ ? { op: '$remFromSet', items: removed, hint: { start, deleteCount }, length: list.__fieldTuples.length }
+ : items.length && !deleteCount && start === list.__fieldTuples.length
+ ? { op: '$addToSet', items, length: list.__fieldTuples.length }
+ : undefined
+ );
+ return res.map(toRealField);
+ }),
+ unshift(...items: any[]) {
+ items = items.map(toObjectField);
+ const list = this[Self];
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this, i, item);
+ }
+ }
+ const res = this[Self].__fieldTuples.unshift(...items);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ /// Accessor methods
+ concat: action(function (this: any, ...items: any[]) {
+ this[Self].__realFields();
+ return this[Self].__fieldTuples.map(toRealField).concat(...items);
+ }),
+ includes(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields().includes(valueToFind, fromIndex);
+ } else {
+ return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
+ }
+ },
+ indexOf(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields().indexOf(valueToFind, fromIndex);
+ }
+ return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
+ },
+ join(separator: any) {
+ this[Self].__realFields();
+ return this[Self].__fieldTuples.map(toRealField).join(separator);
+ },
+ lastElement() {
+ return this[Self].__realFields().lastElement();
+ },
+ lastIndexOf(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
+ } else {
+ return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
+ }
+ },
+ slice(begin: number, end: number) {
+ this[Self].__realFields();
+ return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
+ },
+
+ /// Iteration methods
+ entries() {
+ return this[Self].__realFields().entries();
+ },
+ every(callback: any, thisArg: any) {
+ return this[Self].__realFields().every(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ filter(callback: any, thisArg: any) {
+ return this[Self].__realFields().filter(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ find(callback: any, thisArg: any) {
+ return this[Self].__realFields().find(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ findIndex(callback: any, thisArg: any) {
+ return this[Self].__realFields().findIndex(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ forEach(callback: any, thisArg: any) {
+ return this[Self].__realFields().forEach(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ map(callback: any, thisArg: any) {
+ return this[Self].__realFields().map(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ reduce(callback: any, initialValue: any) {
+ return this[Self].__realFields().reduce(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ reduceRight(callback: any, initialValue: any) {
+ return this[Self].__realFields().reduceRight(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ some(callback: any, thisArg: any) {
+ return this[Self].__realFields().some(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ values() {
+ return this[Self].__realFields().values();
+ },
+ [Symbol.iterator]() {
+ return this[Self].__realFields().values();
+ },
+ };
+ static listGetter(target: any, prop: string | symbol, receiver: any): any {
+ if (ListImpl.listHandlers.hasOwnProperty(prop)) {
+ return ListImpl.listHandlers[prop];
+ }
+ return getter(target, prop, receiver);
+ }
constructor(fields?: T[]) {
super();
const list = new Proxy<this>(this, {
set: setter,
- get: listGetter,
+ get: ListImpl.listGetter,
ownKeys: target => Object.keys(target.__fieldTuples),
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target[FieldTuples]) {
@@ -270,9 +251,9 @@ class ListImpl<T extends Field> extends ObjectField {
throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
},
});
- this[SelfProxy] = list;
+ this[SelfProxy] = list as any as List<Field>; // bcz: ugh .. don't know how to convince typesecript that list is a List
if (fields) {
- (list as any).push(...fields);
+ this[SelfProxy].push(...fields);
}
return list;
}
@@ -305,10 +286,10 @@ class ListImpl<T extends Field> extends ObjectField {
private set __fieldTuples(value) {
this[FieldTuples] = value;
for (const key in value) {
- const field = value[key];
- if (field instanceof ObjectField) {
- field[Parent] = this[Self];
- field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ const item = value[key];
+ if (item instanceof ObjectField) {
+ item[Parent] = this[Self];
+ item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], Number(key), item);
}
}
}
@@ -322,16 +303,8 @@ class ListImpl<T extends Field> extends ObjectField {
// @serializable(alias("fields", list(autoObject())))
@observable
private [FieldTuples]: StoredType<T>[] = [];
-
- private [Update] = (diff: any) => {
- // console.log(diff);
- const update = this[OnUpdate];
- // update && update(diff);
- update?.(diff);
- };
-
private [Self] = this;
- private [SelfProxy]: any;
+ private [SelfProxy]: List<Field>; // also used in utils.ts even though it won't be found using find all references
[ToScriptString]() {
return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;