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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
import { UndoManager } from "../client/util/UndoManager";
import { Doc, Field, FieldResult } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField } from "./Proxy";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action } from "mobx";
import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
import { DocServer } from "../client/DocServer";
import { CurrentUserUtils } from "../server/authentication/models/current_user_utils";
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
}
export interface GetterResult {
value: FieldResult;
shouldReturn?: boolean;
}
export type GetterPlugin = (receiver: any, prop: string | number, currentValue: any) => GetterResult | undefined;
const getterPlugins: GetterPlugin[] = [];
export namespace Plugins {
export function addGetterPlugin(plugin: GetterPlugin) {
getterPlugins.push(plugin);
}
}
const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
//console.log("-set " + target[SelfProxy].title + "(" + target[SelfProxy][prop] + ")." + prop.toString() + " = " + value);
if (SerializationHelper.IsSerializing()) {
target[prop] = value;
return true;
}
if (typeof prop === "symbol") {
target[prop] = value;
return true;
}
if (value !== undefined) {
value = value[SelfProxy] || value;
}
const curValue = target.__fields[prop];
if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
// TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
// 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) {
value = new ProxyField(value);
}
if (value instanceof ObjectField) {
if (value[Parent] && value[Parent] !== receiver) {
throw new Error("Can't put the same object in multiple documents at the same time");
}
value[Parent] = receiver;
value[OnUpdate] = updateFunction(target, prop, value, receiver);
}
if (curValue instanceof ObjectField) {
delete curValue[Parent];
delete curValue[OnUpdate];
}
if (value === undefined) {
delete target.__fields[prop];
} else {
target.__fields[prop] = value;
}
const writeMode = DocServer.getFieldWriteMode(prop as string);
if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
if (!writeMode || (writeMode === DocServer.WriteMode.SameUser && receiver.author === CurrentUserUtils.email)) {
if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string);
}
UndoManager.AddEvent({
redo: () => receiver[prop] = value,
undo: () => receiver[prop] = curValue
});
return true;
});
let _setter: (target: any, prop: string | symbol | number, value: any, receiver: any) => boolean = _setterImpl;
export function makeReadOnly() {
_setter = _readOnlySetter;
}
export function makeEditable() {
_setter = _setterImpl;
}
export function setter(target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
return _setter(target, prop, value, receiver);
}
export function getter(target: any, prop: string | symbol | number, receiver: any): any {
if (prop === "then") {//If we're being awaited
return undefined;
}
if (typeof prop === "symbol") {
return target.__fields[prop] || target[prop];
}
if (SerializationHelper.IsSerializing()) {
return target[prop];
}
return getFieldImpl(target, prop, receiver);
}
function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreProto: boolean = false): any {
receiver = receiver || target[SelfProxy];
let field = target.__fields[prop];
for (const plugin of getterPlugins) {
const res = plugin(receiver, prop, field);
if (res === undefined) continue;
if (res.shouldReturn) {
return res.value;
} else {
field = res.value;
}
}
if (field === undefined && !ignoreProto && prop !== "proto") {
const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
if (proto instanceof Doc) {
return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
}
return undefined;
}
return field;
}
export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
return getFieldImpl(target, prop, undefined, ignoreProto);
}
export function deleteProperty(target: any, prop: string | number | symbol) {
if (typeof prop === "symbol") {
delete target[prop];
return true;
}
target[SelfProxy][prop] = undefined;
return true;
}
export function updateFunction(target: any, prop: any, value: any, receiver: any) {
let current = ObjectField.MakeCopy(value);
return (diff?: any) => {
if (true || !diff) {
diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
const oldValue = current;
const newValue = ObjectField.MakeCopy(value);
current = newValue;
UndoManager.AddEvent({
redo() { receiver[prop] = newValue; },
undo() { receiver[prop] = oldValue; }
});
}
target[Update](diff);
};
}
|