diff options
-rw-r--r-- | src/client/util/LinkManager.ts | 2 | ||||
-rw-r--r-- | src/fields/List.ts | 20 | ||||
-rw-r--r-- | src/fields/Proxy.ts | 68 |
3 files changed, 37 insertions, 53 deletions
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index eacfa5506..588664dec 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -65,7 +65,7 @@ export class LinkManager { }; const watchUserLinkDB = (userLinkDBDoc: Doc) => { LinkManager.links.push(...DocListCast(userLinkDBDoc.data)); - const toRealField = (field: Field) => (field instanceof ProxyField ? field.value() : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields + const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields if (userLinkDBDoc.data) { observe( userLinkDBDoc.data, diff --git a/src/fields/List.ts b/src/fields/List.ts index edaa16003..1e1adc7a8 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -210,7 +210,7 @@ function toObjectField(field: Field) { } function toRealField(field: Field) { - return field instanceof ProxyField ? field.value() : field; + return field instanceof ProxyField ? field.value : field; } function listGetter(target: any, prop: string | number | symbol, receiver: any): any { @@ -271,25 +271,17 @@ class ListImpl<T extends Field> extends ObjectField { // this requests all ProxyFields at the same time to avoid the overhead // of separate network requests and separate updates to the React dom. private __realFields() { - const promised = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()).map(f => ({ field: f as any, promisedFieldId: f instanceof ProxyField ? f.promisedValue() : '' })); + const unrequested = this.__fields.filter(f => f instanceof ProxyField && f.needsRequesting).map(f => f as ProxyField<RefField>); // if we find any ProxyFields that don't have a current value, then // start the server request for all of them - if (promised.length) { - const batchPromise = DocServer.GetRefFields(promised.map(p => p.promisedFieldId)); + if (unrequested.length) { + const batchPromise = DocServer.GetRefFields(unrequested.map(p => p.fieldId)); // as soon as we get the fields from the server, set all the list values in one // action to generate one React dom update. - batchPromise.then( - action(pfields => { - for (let i = 0; i < promised.length; i++) { - promised[i].field.setValue(pfields[promised[i].promisedFieldId]); - } - }) - ); + const allSetPromise = batchPromise.then(action(pfields => unrequested.map(toReq => toReq.setValue(pfields[toReq.fieldId])))); // we also have to mark all lists items with this promise so that any calls to them // will await the batch request and return the requested field value. - // This assumes the handler for 'promise' in the call above being invoked before the - // handler for 'promise' in the lines below. - promised.forEach(p => p.field.setPromise(batchPromise.then(pfields => pfields[p.promisedFieldId]))); + unrequested.forEach(p => p.setExternalValuePromise(allSetPromise)); } return this.__fields.map(toRealField); } diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts index 3aafacd96..1266e6e3c 100644 --- a/src/fields/Proxy.ts +++ b/src/fields/Proxy.ts @@ -1,7 +1,7 @@ import { Deserializable } from '../client/util/SerializationHelper'; -import { FieldWaiting } from './Doc'; +import { FieldWaiting, Opt } from './Doc'; import { primitive, serializable } from 'serializr'; -import { observable, action, runInAction } from 'mobx'; +import { observable, action, runInAction, computed } from 'mobx'; import { DocServer } from '../client/DocServer'; import { RefField } from './RefField'; import { ObjectField } from './ObjectField'; @@ -10,8 +10,8 @@ import { scriptingGlobal } from '../client/util/ScriptingGlobals'; import { Plugins } from './util'; function deserializeProxy(field: any) { - if (!field.cache) { - field.cache = DocServer.GetCachedRefField(field.fieldId) as any; + if (!field.cache.field) { + field.cache = { field: DocServer.GetCachedRefField(field.fieldId) as any, p: undefined }; } } @Deserializable('proxy', deserializeProxy) @@ -25,13 +25,13 @@ export class ProxyField<T extends RefField> extends ObjectField { //this.cache = DocServer.GetCachedRefField(value) as any; this.fieldId = value; } else if (value) { - this.cache = value; + this.cache = { field: value, p: undefined }; this.fieldId = value[Id]; } } [Copy]() { - if (this.cache) return new ProxyField<T>(this.cache); + if (this.cache.field) return new ProxyField<T>(this.cache.field); return new ProxyField<T>(this.fieldId); } @@ -48,48 +48,40 @@ export class ProxyField<T extends RefField> extends ObjectField { // This getter/setter and nested object thing is // because mobx doesn't play well with observable proxies @observable.ref - private _cache: { readonly field: T | undefined } = { field: undefined }; - private get cache(): T | undefined { - return this._cache.field; + private _cache: { readonly field: T | undefined; p: FieldWaiting<T> | undefined } = { field: undefined, p: undefined }; + private get cache(): { field: T | undefined; p: FieldWaiting<T> | undefined } { + return this._cache; } - private set cache(field: T | undefined) { - this._cache = { field }; + private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) { + runInAction(() => (this._cache = { ...val })); } private failed = false; - private promise?: Promise<any>; - @action - value(): T | undefined | FieldWaiting<T> { - if (this.cache) return this.cache; + @computed get value(): T | undefined | FieldWaiting<T> { + if (this.cache.field) return this.cache.field; if (this.failed) return undefined; - const cached = DocServer.GetCachedRefField(this.fieldId) as T; - if (cached !== undefined) { - //setTimeout(action(() => (this.cache = cached))); // can't do this because it triggers too many invalidations while rendering. - } else if (!this.promise) { - this.promise = DocServer.GetRefField(this.fieldId).then( - action((field: any) => { - this.promise = undefined; - this.cache = field; - this.failed = field === undefined; - return field; - }) - ) as FieldWaiting<T>; + this.cache.field = DocServer.GetCachedRefField(this.fieldId) as T; + if (!this.cache.field && !this.cache.p) { + this.cache = { + field: undefined, + p: DocServer.GetRefField(this.fieldId).then(val => this.setValue(val as T)) as FieldWaiting<T>, + }; } - return cached ?? this.promise; + return this.cache.field ?? this.cache.p; } - promisedValue(): string { - return !this.cache && !this.failed && !this.promise && !DocServer.GetCachedRefField(this.fieldId) ? this.fieldId : ''; + @computed get needsRequesting(): boolean { + return !this.cache.field && !this.failed && !this._cache.p && !DocServer.GetCachedRefField(this.fieldId) ? true : false; } - setPromise(promise: any) { - this.promise = promise; + + setExternalValuePromise(externalValuePromise: Promise<any>) { + this.cache.p = externalValuePromise.then(() => this.value) as FieldWaiting<T>; } @action - setValue(field: any) { - this.promise = undefined; - this.cache = field; - if (field === undefined) this.failed = true; + setValue(field: Opt<T>) { + this.cache = { field, p: undefined }; + this.failed = field === undefined; return field; } } @@ -116,14 +108,14 @@ export namespace ProxyField { export function initPlugin() { Plugins.addGetterPlugin((doc, _, value) => { if (useProxy && value instanceof ProxyField) { - return { value: value.value() }; + return { value: value.value }; } }); } } function prefetchValue(proxy: PrefetchProxy<RefField>) { - return proxy.value() as any; + return proxy.value as any; } @scriptingGlobal |