import { Deserializable } from '../client/util/SerializationHelper'; import { Field, FieldWaiting, Opt } from './Doc'; import { primitive, serializable } from 'serializr'; import { observable, action, runInAction, computed } from 'mobx'; import { DocServer } from '../client/DocServer'; import { RefField } from './RefField'; import { ObjectField } from './ObjectField'; import { Id, Copy, ToScriptString, ToString, ToValue } from './FieldSymbols'; import { scriptingGlobal } from '../client/util/ScriptingGlobals'; function deserializeProxy(field: any) { if (!field.cache.field) { field.cache = { field: DocServer.GetCachedRefField(field.fieldId) as any, p: undefined }; } } @Deserializable('proxy', deserializeProxy) export class ProxyField extends ObjectField { constructor(); constructor(value: T); constructor(fieldId: string); constructor(value?: T | string) { super(); if (typeof value === 'string') { //this.cache = DocServer.GetCachedRefField(value) as any; this.fieldId = value; } else if (value) { this.cache = { field: value, p: undefined }; this.fieldId = value[Id]; } } [ToValue](doc: any) { return ProxyField.toValue(this); } [Copy]() { if (this.cache.field) return new ProxyField(this.cache.field); return new ProxyField(this.fieldId); } [ToScriptString]() { return Field.toScriptString(this[ToValue](undefined)?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ? } [ToString]() { return 'ProxyField'; } @serializable(primitive()) readonly fieldId: string = ''; // 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; p: FieldWaiting | undefined } = { field: undefined, p: undefined }; private get cache(): { field: T | undefined; p: FieldWaiting | undefined } { return this._cache; } private set cache(val: { field: T | undefined; p: FieldWaiting | undefined }) { runInAction(() => (this._cache = { ...val })); } private failed = false; @computed get value(): T | undefined | FieldWaiting { if (this.cache.field) return this.cache.field; if (this.failed) return undefined; 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, }; } return this.cache.field ?? this.cache.p; } @computed get needsRequesting(): boolean { return !this.cache.field && !this.failed && !this._cache.p && !DocServer.GetCachedRefField(this.fieldId) ? true : false; } setExternalValuePromise(externalValuePromise: Promise) { this.cache.p = externalValuePromise.then(() => this.value) as FieldWaiting; } @action setValue(field: Opt) { this.cache = { field, p: undefined }; this.failed = field === undefined; return field; } } export namespace ProxyField { let useProxy = true; export function DisableProxyFields() { useProxy = false; } export function EnableProxyFields() { useProxy = true; } export function WithoutProxy(fn: () => T) { DisableProxyFields(); try { return fn(); } finally { EnableProxyFields(); } } export function toValue(value: any) { if (useProxy) { return { value: value.value }; } } } function prefetchValue(proxy: PrefetchProxy) { return proxy.value as any; } @scriptingGlobal @Deserializable('prefetch_proxy', prefetchValue) export class PrefetchProxy extends ProxyField {}