aboutsummaryrefslogtreecommitdiff
path: root/src/fields/Proxy.ts
blob: 1266e6e3c2de388313807ea8f3e34fe61df090a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { Deserializable } from '../client/util/SerializationHelper';
import { 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 } from './FieldSymbols';
import { scriptingGlobal } from '../client/util/ScriptingGlobals';
import { Plugins } from './util';

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<T extends RefField> 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];
        }
    }

    [Copy]() {
        if (this.cache.field) return new ProxyField<T>(this.cache.field);
        return new ProxyField<T>(this.fieldId);
    }

    [ToScriptString]() {
        return '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<T> | undefined } = { field: undefined, p: undefined };
    private get cache(): { field: T | undefined; p: FieldWaiting<T> | undefined } {
        return this._cache;
    }
    private set cache(val: { field: T | undefined; p: FieldWaiting<T> | undefined }) {
        runInAction(() => (this._cache = { ...val }));
    }

    private failed = false;

    @computed get value(): T | undefined | FieldWaiting<T> {
        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<T>,
            };
        }
        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<any>) {
        this.cache.p = externalValuePromise.then(() => this.value) as FieldWaiting<T>;
    }
    @action
    setValue(field: Opt<T>) {
        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<T>(fn: () => T) {
        DisableProxyFields();
        try {
            return fn();
        } finally {
            EnableProxyFields();
        }
    }

    export function initPlugin() {
        Plugins.addGetterPlugin((doc, _, value) => {
            if (useProxy && value instanceof ProxyField) {
                return { value: value.value };
            }
        });
    }
}

function prefetchValue(proxy: PrefetchProxy<RefField>) {
    return proxy.value as any;
}

@scriptingGlobal
@Deserializable('prefetch_proxy', prefetchValue)
export class PrefetchProxy<T extends RefField> extends ProxyField<T> {}