aboutsummaryrefslogtreecommitdiff
path: root/src/fields
diff options
context:
space:
mode:
Diffstat (limited to 'src/fields')
-rw-r--r--src/fields/Doc.ts3
-rw-r--r--src/fields/List.ts72
-rw-r--r--src/fields/ScriptField.ts176
-rw-r--r--src/fields/URLField.ts69
-rw-r--r--src/fields/documentSchemas.ts182
-rw-r--r--src/fields/util.ts59
6 files changed, 307 insertions, 254 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 0c7504913..8d56ebf8c 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -211,6 +211,7 @@ export class Doc extends RefField {
}
public static set ActivePage(val) {
Doc.UserDoc().activePage = val;
+ DocServer.UPDATE_SERVER_CACHE();
}
public static get ActiveDashboard() {
return DocCast(Doc.UserDoc().activeDashboard);
@@ -228,7 +229,7 @@ export class Doc extends RefField {
return DocCast(Doc.UserDoc().activePresentation);
}
public static set ActivePresentation(val) {
- Doc.UserDoc().activePresentation = val;
+ Doc.UserDoc().activePresentation = new PrefetchProxy(val);
}
constructor(id?: FieldId, forceSave?: boolean) {
super(id);
diff --git a/src/fields/List.ts b/src/fields/List.ts
index b15548327..5cc4ca543 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -1,25 +1,25 @@
-import { action, observable } from "mobx";
-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 { Field } from "./Doc";
-import { Copy, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } 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";
+import { action, observable } from 'mobx';
+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 { Field } from './Doc';
+import { Copy, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } 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");
+ 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");
+ throw new Error('fill with RefFields not supported yet');
}
const res = this[Self].__fields.fill(value, start, end);
this[Update]();
@@ -44,7 +44,7 @@ const listHandlers: any = {
}
}
const res = list.__fields.push(...items);
- this[Update]({ op: "$addToSet", items, length: length + items.length });
+ this[Update]({ op: '$addToSet', items, length: length + items.length });
return res;
}),
reverse() {
@@ -78,8 +78,13 @@ const listHandlers: any = {
}
}
const res = list.__fields.splice(start, deleteCount, ...items);
- this[Update](items.length === 0 && deleteCount ? { op: "$remFromSet", items: removed, length: list.__fields.length } :
- items.length && !deleteCount && start === list.__fields.length ? { op: "$addToSet", items, length: list.__fields.length } : undefined);
+ this[Update](
+ items.length === 0 && deleteCount
+ ? { op: '$remFromSet', items: removed, length: list.__fields.length }
+ : items.length && !deleteCount && start === list.__fields.length
+ ? { op: '$addToSet', items, length: list.__fields.length }
+ : undefined
+ );
return res.map(toRealField);
}),
unshift(...items: any[]) {
@@ -98,7 +103,6 @@ const listHandlers: any = {
const res = this[Self].__fields.unshift(...items);
this[Update]();
return res;
-
},
/// Accessor methods
concat: action(function (this: any, ...items: any[]) {
@@ -198,7 +202,7 @@ const listHandlers: any = {
},
[Symbol.iterator]() {
return this[Self].__realFields().values();
- }
+ },
};
function toObjectField(field: Field) {
@@ -217,14 +221,14 @@ function listGetter(target: any, prop: string | number | symbol, receiver: any):
}
interface ListSpliceUpdate<T> {
- type: "splice";
+ type: 'splice';
index: number;
added: T[];
removedCount: number;
}
interface ListIndexUpdate<T> {
- type: "update";
+ type: 'update';
index: number;
newValue: T;
}
@@ -233,7 +237,7 @@ type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
-@Deserializable("list")
+@Deserializable('list')
class ListImpl<T extends Field> extends ObjectField {
constructor(fields?: T[]) {
super();
@@ -244,14 +248,16 @@ class ListImpl<T extends Field> extends ObjectField {
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target.__fields) {
return {
- configurable: true,//TODO Should configurable be true?
+ configurable: true, //TODO Should configurable be true?
enumerable: true,
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
},
deleteProperty: deleteProperty,
- defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+ defineProperty: () => {
+ throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
+ },
});
this[SelfProxy] = list;
if (fields) {
@@ -265,7 +271,7 @@ 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 promised = this.__fields.filter(f => f instanceof ProxyField && f.promisedValue()).map(f => ({ field: f as any, promisedFieldId: f instanceof ProxyField ? f.promisedValue() : '' }));
// if we find any ProxyFields that don't have a current value, then
// start the server request for all of them
if (promised.length) {
@@ -282,7 +288,7 @@ class ListImpl<T extends Field> extends ObjectField {
return this.__fields.map(toRealField);
}
- @serializable(alias("fields", list(autoObject(), { afterDeserialize: afterDocDeserialize })))
+ @serializable(alias('fields', list(autoObject(), { afterDeserialize: afterDocDeserialize })))
private get __fields() {
return this.___fields;
}
@@ -299,7 +305,7 @@ class ListImpl<T extends Field> extends ObjectField {
}
[Copy]() {
- const copiedData = this[Self].__fields.map(f => f instanceof ObjectField ? f[Copy]() : f);
+ const copiedData = this[Self].__fields.map(f => (f instanceof ObjectField ? f[Copy]() : f));
const deepCopy = new ListImpl<T>(copiedData as any);
return deepCopy;
}
@@ -313,7 +319,7 @@ class ListImpl<T extends Field> extends ObjectField {
const update = this[OnUpdate];
// update && update(diff);
update?.(diff);
- }
+ };
private [Self] = this;
private [SelfProxy]: any;
@@ -328,9 +334,9 @@ class ListImpl<T extends Field> extends ObjectField {
export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any;
-ScriptingGlobals.add("List", List);
+ScriptingGlobals.add('List', List);
ScriptingGlobals.add(function compareLists(l1: any, l2: any) {
- const L1 = Cast(l1, listSpec("string"), []);
- const L2 = Cast(l2, listSpec("string"), []);
+ const L1 = Cast(l1, listSpec('string'), []);
+ const L2 = Cast(l2, listSpec('string'), []);
return !L1 && !L2 ? true : L1 && L2 && L1.length === L2.length && L2.reduce((p, v) => p && L1.includes(v), true);
-}, "compare two lists"); \ No newline at end of file
+}, 'compare two lists');
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 40ca0ce22..68fb45987 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,29 +1,32 @@
-import { computedFn } from "mobx-utils";
-import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from "serializr";
-import { CompiledScript, CompileScript } from "../client/util/Scripting";
-import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { autoObject, Deserializable } from "../client/util/SerializationHelper";
-import { numberRange } from "../Utils";
-import { Doc, Field, Opt } from "./Doc";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { List } from "./List";
-import { ObjectField } from "./ObjectField";
-import { ProxyField } from "./Proxy";
-import { Cast, NumCast } from "./Types";
-import { Plugins } from "./util";
+import { computedFn } from 'mobx-utils';
+import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr';
+import { DocServer } from '../client/DocServer';
+import { CompiledScript, CompileScript, ScriptOptions } from '../client/util/Scripting';
+import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { numberRange } from '../Utils';
+import { Doc, Field, Opt } from './Doc';
+import { Copy, Id, ToScriptString, ToString } from './FieldSymbols';
+import { List } from './List';
+import { ObjectField } from './ObjectField';
+import { Cast, NumCast } from './Types';
+import { Plugins } from './util';
function optional(propSchema: PropSchema) {
- return custom(value => {
- if (value !== undefined) {
- return propSchema.serializer(value);
- }
- return SKIP;
- }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
- if (jsonValue !== undefined) {
- return propSchema.deserializer(jsonValue, callback, context, oldValue);
+ return custom(
+ value => {
+ if (value !== undefined) {
+ return propSchema.serializer(value);
+ }
+ return SKIP;
+ },
+ (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
+ if (jsonValue !== undefined) {
+ return propSchema.deserializer(jsonValue, callback, context, oldValue);
+ }
+ return SKIP;
}
- return SKIP;
- });
+ );
}
const optionsSchema = createSimpleSchema({
@@ -32,31 +35,19 @@ const optionsSchema = createSimpleSchema({
typecheck: true,
editable: true,
readonly: true,
- params: optional(map(primitive()))
+ params: optional(map(primitive())),
});
const scriptSchema = createSimpleSchema({
options: object(optionsSchema),
- originalScript: true
+ originalScript: true,
});
-async function deserializeScript(script: ScriptField) {
- const captures: ProxyField<Doc> = (script as any).captures;
- const cache = captures ? undefined : ScriptField.GetScriptFieldCache(script.script.originalScript);
- if (cache) return (script as any).script = cache;
- if (captures) {
- const doc = (await captures.value())!;
- const captured: any = {};
- const keys = Object.keys(doc);
- const vals = await Promise.all(keys.map(key => doc[key]) as any);
- keys.forEach((key, i) => captured[key] = vals[i]);
- (script.script.options as any).capturedVariables = captured;
- }
+function finalizeScript(script: ScriptField, captures: boolean) {
const comp = CompileScript(script.script.originalScript, script.script.options);
if (!comp.compiled) {
throw new Error("Couldn't compile loaded script");
}
- (script as any).script = comp;
!captures && ScriptField._scriptFieldCache.set(script.script.originalScript, comp);
if (script.setterscript) {
const compset = CompileScript(script.setterscript?.originalScript, script.setterscript.options);
@@ -65,32 +56,56 @@ async function deserializeScript(script: ScriptField) {
}
(script as any).setterscript = compset;
}
+ return comp;
+}
+async function deserializeScript(script: ScriptField) {
+ if (script.captures) {
+ const captured: any = {};
+ (script.script.options as ScriptOptions).capturedVariables = captured;
+ Promise.all(
+ script.captures.map(async capture => {
+ const key = capture.split(':')[0];
+ const val = capture.split(':')[1];
+ if (val === 'true') captured[key] = true;
+ else if (val === 'false') captured[key] = false;
+ else if (val.startsWith('ID->')) captured[key] = await DocServer.GetRefField(val.replace('ID->', ''));
+ else if (!isNaN(Number(val))) captured[key] = Number(val);
+ else captured[key] = val;
+ })
+ ).then(() => ((script as any).script = finalizeScript(script, true)));
+ } else {
+ (script as any).script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script, false);
+ }
}
@scriptingGlobal
-@Deserializable("script", deserializeScript)
+@Deserializable('script', deserializeScript)
export class ScriptField extends ObjectField {
+ @serializable
+ readonly rawscript: string | undefined;
@serializable(object(scriptSchema))
readonly script: CompiledScript;
@serializable(object(scriptSchema))
readonly setterscript: CompiledScript | undefined;
@serializable(autoObject())
- private captures?: ProxyField<Doc>;
+ captures?: List<string>;
public static _scriptFieldCache: Map<string, Opt<CompiledScript>> = new Map();
- public static GetScriptFieldCache(field: string) { return this._scriptFieldCache.get(field); }
+ public static GetScriptFieldCache(field: string) {
+ return this._scriptFieldCache.get(field);
+ }
- constructor(script: CompiledScript, setterscript?: CompiledScript) {
+ constructor(script: CompiledScript | undefined, setterscript?: CompiledScript, rawscript?: string) {
super();
- if (script?.options.capturedVariables) {
- const doc = Doc.assign(new Doc, script.options.capturedVariables);
- doc.system = true;
- this.captures = new ProxyField(doc);
+ const captured = script?.options.capturedVariables;
+ if (captured) {
+ this.captures = new List<string>(Object.keys(captured).map(key => key + ':' + (captured[key] instanceof Doc ? 'ID->' + (captured[key] as Doc)[Id] : captured[key].toString())));
}
+ this.rawscript = rawscript;
this.setterscript = setterscript;
- this.script = script;
+ this.script = script ?? (CompileScript('false') as CompiledScript);
}
// init(callback: (res: Field) => any) {
@@ -115,63 +130,62 @@ export class ScriptField extends ObjectField {
// }
[Copy](): ObjectField {
- return new ScriptField(this.script, this.setterscript);
+ return new ScriptField(this.script, this.setterscript, this.rawscript);
}
toString() {
return `${this.script.originalScript} + ${this.setterscript?.originalScript}`;
}
[ToScriptString]() {
- return "script field";
+ return 'script field';
}
[ToString]() {
return this.script.originalScript;
}
- public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) {
+ public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = CompileScript(script, {
params: {
- this: Doc?.name || "Doc", // this is the doc that executes the script
- self: Doc?.name || "Doc", // self is the root doc of the doc that executes the script
- _last_: "any", // _last_ is the previous value of a computed field when it is being triggered to re-run.
- _readOnly_: "boolean", // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox)
- ...params
+ this: Doc?.name || 'Doc', // this is the doc that executes the script
+ self: Doc?.name || 'Doc', // self is the root doc of the doc that executes the script
+ _last_: 'any', // _last_ is the previous value of a computed field when it is being triggered to re-run.
+ _readOnly_: 'boolean', // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox)
+ ...params,
},
typecheck: false,
editable: true,
addReturn: addReturn,
- capturedVariables
+ capturedVariables,
});
return compiled;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
- public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
}
@scriptingGlobal
-@Deserializable("computed", deserializeScript)
+@Deserializable('computed', deserializeScript)
export class ComputedField extends ScriptField {
_lastComputedResult: any;
//TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
- _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result;
-
+ _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
[Copy](): ObjectField {
- return new ComputedField(this.script, this.setterscript);
+ return new ComputedField(this.script, this.setterscript, this.rawscript);
}
public static MakeScript(script: string, params: object = {}) {
const compiled = ScriptField.CompileScript(script, params, false);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
@@ -182,7 +196,7 @@ export class ComputedField extends ScriptField {
doc[`${fieldKey}-indexed`] = flist;
}
const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
- const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: "any" }, true, {});
+ const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
}
}
@@ -196,7 +210,7 @@ export namespace ComputedField {
useComputed = true;
}
- export const undefined = "__undefined";
+ export const undefined = '__undefined';
export function WithoutComputed<T>(fn: () => T) {
DisableComputedFields();
@@ -216,15 +230,27 @@ export namespace ComputedField {
}
}
-ScriptingGlobals.add(function setIndexVal(list: any[], index: number, value: any) {
- while (list.length <= index) list.push(undefined);
- list[index] = value;
-}, "sets the value at a given index of a list", "(list: any[], index: number, value: any)");
+ScriptingGlobals.add(
+ function setIndexVal(list: any[], index: number, value: any) {
+ while (list.length <= index) list.push(undefined);
+ list[index] = value;
+ },
+ 'sets the value at a given index of a list',
+ '(list: any[], index: number, value: any)'
+);
-ScriptingGlobals.add(function getIndexVal(list: any[], index: number) {
- return list?.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any);
-}, "returns the value at a given index of a list", "(list: any[], index: number)");
+ScriptingGlobals.add(
+ function getIndexVal(list: any[], index: number) {
+ return list?.reduce((p, x, i) => ((i <= index && x !== undefined) || p === undefined ? x : p), undefined as any);
+ },
+ 'returns the value at a given index of a list',
+ '(list: any[], index: number)'
+);
-ScriptingGlobals.add(function makeScript(script: string) {
- return ScriptField.MakeScript(script);
-}, "returns the value at a given index of a list", "(list: any[], index: number)");
+ScriptingGlobals.add(
+ function makeScript(script: string) {
+ return ScriptField.MakeScript(script);
+ },
+ 'returns the value at a given index of a list',
+ '(list: any[], index: number)'
+);
diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts
index 36dd56a1a..00c78e231 100644
--- a/src/fields/URLField.ts
+++ b/src/fields/URLField.ts
@@ -1,16 +1,14 @@
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, custom } from "serializr";
-import { ObjectField } from "./ObjectField";
-import { ToScriptString, ToString, Copy } from "./FieldSymbols";
-import { scriptingGlobal } from "../client/util/ScriptingGlobals";
-import { Utils } from "../Utils";
+import { Deserializable } from '../client/util/SerializationHelper';
+import { serializable, custom } from 'serializr';
+import { ObjectField } from './ObjectField';
+import { ToScriptString, ToString, Copy } from './FieldSymbols';
+import { scriptingGlobal } from '../client/util/ScriptingGlobals';
+import { Utils } from '../Utils';
function url() {
return custom(
function (value: URL) {
- return value.origin === window.location.origin ?
- value.pathname :
- value.href;
+ return value?.origin === window.location.origin ? value.pathname : value?.href;
},
function (jsonValue: string) {
return new URL(jsonValue, window.location.origin);
@@ -26,23 +24,23 @@ export abstract class URLField extends ObjectField {
constructor(url: URL);
constructor(url: URL | string) {
super();
- if (typeof url === "string") {
- url = url.startsWith("http") ? new URL(url) : new URL(url, window.location.origin);
+ if (typeof url === 'string') {
+ url = url.startsWith('http') ? new URL(url) : new URL(url, window.location.origin);
}
this.url = url;
}
[ToScriptString]() {
- if (Utils.prepend(this.url.pathname) === this.url.href) {
+ if (Utils.prepend(this.url?.pathname) === this.url?.href) {
return `new ${this.constructor.name}("${this.url.pathname}")`;
}
return `new ${this.constructor.name}("${this.url.href}")`;
}
[ToString]() {
- if (Utils.prepend(this.url.pathname) === this.url.href) {
+ if (Utils.prepend(this.url?.pathname) === this.url?.href) {
return this.url.pathname;
}
- return this.url.href;
+ return this.url?.href;
}
[Copy](): this {
@@ -50,16 +48,35 @@ export abstract class URLField extends ObjectField {
}
}
-export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short.ogg";
-
-@scriptingGlobal @Deserializable("audio") export class AudioField extends URLField { }
-@scriptingGlobal @Deserializable("recording") export class RecordingField extends URLField { }
-@scriptingGlobal @Deserializable("image") export class ImageField extends URLField { }
-@scriptingGlobal @Deserializable("video") export class VideoField extends URLField { }
-@scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { }
-@scriptingGlobal @Deserializable("web") export class WebField extends URLField { }
-@scriptingGlobal @Deserializable("map") export class MapField extends URLField { }
-@scriptingGlobal @Deserializable("csv") export class CsvField extends URLField { }
-@scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { }
-@scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { }
+export const nullAudio = 'https://actions.google.com/sounds/v1/alarms/beep_short.ogg';
+@scriptingGlobal
+@Deserializable('audio')
+export class AudioField extends URLField {}
+@scriptingGlobal
+@Deserializable('recording')
+export class RecordingField extends URLField {}
+@scriptingGlobal
+@Deserializable('image')
+export class ImageField extends URLField {}
+@scriptingGlobal
+@Deserializable('video')
+export class VideoField extends URLField {}
+@scriptingGlobal
+@Deserializable('pdf')
+export class PdfField extends URLField {}
+@scriptingGlobal
+@Deserializable('web')
+export class WebField extends URLField {}
+@scriptingGlobal
+@Deserializable('map')
+export class MapField extends URLField {}
+@scriptingGlobal
+@Deserializable('csv')
+export class CsvField extends URLField {}
+@scriptingGlobal
+@Deserializable('youtube')
+export class YoutubeField extends URLField {}
+@scriptingGlobal
+@Deserializable('webcam')
+export class WebCamField extends URLField {}
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index be39e0709..24b5a359d 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -1,116 +1,114 @@
-import { makeInterface, createSchema, listSpec } from "./Schema";
-import { ScriptField } from "./ScriptField";
-import { Doc } from "./Doc";
-import { DateField } from "./DateField";
-import { SchemaHeaderField } from "./SchemaHeaderField";
+import { makeInterface, createSchema, listSpec } from './Schema';
+import { ScriptField } from './ScriptField';
+import { Doc } from './Doc';
+import { DateField } from './DateField';
+import { SchemaHeaderField } from './SchemaHeaderField';
export const documentSchema = createSchema({
// content properties
- type: "string", // enumerated type of document -- should be template-specific (ie, start with an '_')
- title: "string", // document title (can be on either data document or layout)
- isTemplateForField: "string",// if specified, it indicates the document is a template that renders the specified field
- creationDate: DateField, // when the document was created
- links: listSpec(Doc), // computed (readonly) list of links associated with this document
+ type: 'string', // enumerated type of document -- should be template-specific (ie, start with an '_')
+ title: 'string', // document title (can be on either data document or layout)
+ isTemplateForField: 'string', // if specified, it indicates the document is a template that renders the specified field
+ creationDate: DateField, // when the document was created
+ links: listSpec(Doc), // computed (readonly) list of links associated with this document
// "Location" properties in a very general sense
- _curPage: "number", // current page of a page based document
- _currentFrame: "number", // current frame of a frame based collection (e.g., a progressive slide)
- lastFrame: "number", // last frame of a frame based collection (e.g., a progressive slide)
- activeFrame: "number", // the active frame of a frame based animated document
- _currentTimecode: "number", // current play back time of a temporal document (video / audio)
- _timecodeToShow: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
- _timecodeToHIde: "number", // the time that a document should be hidden
- markers: listSpec(Doc), // list of markers for audio / video
- x: "number", // x coordinate when in a freeform view
- y: "number", // y coordinate when in a freeform view
- z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview
- zIndex: "number", // zIndex of a document in a freeform view
- _scrollTop: "number", // scroll position of a scrollable document (pdf, text, web)
- lat: "number",
- lng: "number",
+ _curPage: 'number', // current page of a page based document
+ _currentFrame: 'number', // current frame of a frame based collection (e.g., a progressive slide)
+ lastFrame: 'number', // last frame of a frame based collection (e.g., a progressive slide)
+ activeFrame: 'number', // the active frame of a frame based animated document
+ _currentTimecode: 'number', // current play back time of a temporal document (video / audio)
+ _timecodeToShow: 'number', // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+ _timecodeToHIde: 'number', // the time that a document should be hidden
+ markers: listSpec(Doc), // list of markers for audio / video
+ x: 'number', // x coordinate when in a freeform view
+ y: 'number', // y coordinate when in a freeform view
+ z: 'number', // z "coordinate" - non-zero specifies the overlay layer of a freeformview
+ zIndex: 'number', // zIndex of a document in a freeform view
+ _scrollTop: 'number', // scroll position of a scrollable document (pdf, text, web)
+ lat: 'number',
+ lng: 'number',
// appearance properties on the layout
- "_backgroundGrid-spacing": "number", // the size of the grid for collection views
- _autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents
- _nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set
- _nativeHeight: "number", // "
- _width: "number", // width of document in its container's coordinate system
- _height: "number", // "
- _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitContentsToBox is set
- _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitContentsToBox is set
- _xMargin: "number", // margin added on left/right of most documents to add separation from their container
- _yMargin: "number", // margin added on top/bottom of most documents to add separation from their container
- _overflow: "string", // sets overflow behvavior for CollectionFreeForm views
- _showCaption: "string", // whether editable caption text is overlayed at the bottom of the document
- _showTitle: "string", // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover
- _showAudio: "boolean", // whether to show the audio record icon on documents
- _pivotField: "string", // specifies which field key should be used as the timeline/pivot axis
- _columnsFill: "boolean", // whether documents in a stacking view column should be sized to fill the column
- _columnsSort: "string", // how a document should be sorted "ascending", "descending", undefined (none)
- _columnsHideIfEmpty: "boolean", // whether empty stacking view column headings should be hidden
+ '_backgroundGrid-spacing': 'number', // the size of the grid for collection views
+ _autoHeight: 'boolean', // whether the height of the document should be computed automatically based on its contents
+ _nativeWidth: 'number', // native width of document which determines how much document contents are scaled when the document's width is set
+ _nativeHeight: 'number', // "
+ _width: 'number', // width of document in its container's coordinate system
+ _height: 'number', // "
+ _xPadding: 'number', // pixels of padding on left/right of collectionfreeformview contents when fitContentsToBox is set
+ _yPadding: 'number', // pixels of padding on top/bottom of collectionfreeformview contents when fitContentsToBox is set
+ _xMargin: 'number', // margin added on left/right of most documents to add separation from their container
+ _yMargin: 'number', // margin added on top/bottom of most documents to add separation from their container
+ _overflow: 'string', // sets overflow behvavior for CollectionFreeForm views
+ _showCaption: 'string', // whether editable caption text is overlayed at the bottom of the document
+ _showTitle: 'string', // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover
+ _pivotField: 'string', // specifies which field key should be used as the timeline/pivot axis
+ _columnsFill: 'boolean', // whether documents in a stacking view column should be sized to fill the column
+ _columnsSort: 'string', // how a document should be sorted "ascending", "descending", undefined (none)
+ _columnsHideIfEmpty: 'boolean', // whether empty stacking view column headings should be hidden
_columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry
_schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views
- _fontSize: "string",
- _fontFamily: "string",
- _sidebarWidthPercent: "string", // percent of text window width taken up by sidebar
+ _fontSize: 'string',
+ _fontFamily: 'string',
+ _sidebarWidthPercent: 'string', // percent of text window width taken up by sidebar
// appearance properties on the data document
- backgroundColor: "string", // background color of document
- borderRounding: "string", // border radius rounding of document
- boxShadow: "string", // the amount of shadow around the perimeter of a document
- color: "string", // foreground color of document
- fitContentsToBox: "boolean",// whether freeform view contents should be zoomed/panned to fill the area of the document view box
- fontSize: "string",
- hidden: "boolean", // whether a document should not be displayed
- isInkMask: "boolean", // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through)
- layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below
- layoutKey: "string", // holds the field key for the field that actually holds the current lyoat
- letterSpacing: "string",
- opacity: "number", // opacity of document
- strokeWidth: "number",
- strokeBezier: "number",
- strokeStartMarker: "string",
- strokeEndMarker: "string",
- strokeDash: "string",
- textTransform: "string",
- treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden
- treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
- treeViewExpandedViewLock: "boolean", // whether the expanded view can be changed
- treeViewOpenIsTransient: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
- treeViewType: "string", // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off
+ backgroundColor: 'string', // background color of document
+ borderRounding: 'string', // border radius rounding of document
+ boxShadow: 'string', // the amount of shadow around the perimeter of a document
+ color: 'string', // foreground color of document
+ fitContentsToBox: 'boolean', // whether freeform view contents should be zoomed/panned to fill the area of the document view box
+ fontSize: 'string',
+ hidden: 'boolean', // whether a document should not be displayed
+ isInkMask: 'boolean', // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through)
+ layout: 'string', // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below
+ layoutKey: 'string', // holds the field key for the field that actually holds the current lyoat
+ letterSpacing: 'string',
+ opacity: 'number', // opacity of document
+ strokeWidth: 'number',
+ strokeBezier: 'number',
+ strokeStartMarker: 'string',
+ strokeEndMarker: 'string',
+ strokeDash: 'string',
+ textTransform: 'string',
+ treeViewOpen: 'boolean', // flag denoting whether the documents sub-tree (contents) is visible or hidden
+ treeViewExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree
+ treeViewExpandedViewLock: 'boolean', // whether the expanded view can be changed
+ treeViewOpenIsTransient: 'boolean', // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+ treeViewType: 'string', // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off
// interaction and linking properties
- ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events)
- onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
+ ignoreClick: 'boolean', // whether documents ignores input clicks (but does not ignore manipulation and other events)
+ onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
- onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
- onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped.
- followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, )
- hideLinkButton: "boolean", // whether the blue link counter button should be hidden
- hideAllLinks: "boolean", // whether all individual blue anchor dots should be hidden
- linkDisplay: "boolean", // whether a link connection should be shown between link anchor endpoints.
- isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
- isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked
- layers: listSpec("string"), // which layers the document is part of
- _lockedPosition: "boolean", // whether the document can be moved (dragged)
- _lockedTransform: "boolean",// whether a freeformview can pan/zoom
- displayArrow: "boolean", // toggles directed arrows
+ onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
+ onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped.
+ followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, )
+ hideLinkButton: 'boolean', // whether the blue link counter button should be hidden
+ hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden
+ linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints.
+ isInPlaceContainer: 'boolean', // whether the marked object will display addDocTab() calls that target "inPlace" destinations
+ isLinkButton: 'boolean', // whether document functions as a link follow button to follow the first link on the document when clicked
+ layers: listSpec('string'), // which layers the document is part of
+ _lockedPosition: 'boolean', // whether the document can be moved (dragged)
+ _lockedTransform: 'boolean', // whether a freeformview can pan/zoom
+ displayArrow: 'boolean', // toggles directed arrows
// drag drop properties
- _stayInCollection: "boolean",// whether document can be dropped into a different collection
- dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
- dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
- targetDropAction: "string", // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
- childDropAction: "string", // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
- removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
+ _stayInCollection: 'boolean', // whether document can be dropped into a different collection
+ dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
+ dropAction: 'string', // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
+ targetDropAction: 'string', // allows the target of a drop event to specify the dropAction ("alias", "copy", "move") NOTE: if the document is dropped within the same collection, the dropAction is coerced to 'move'
+ childDropAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "alias" or "copy")
+ removeDropProperties: listSpec('string'), // properties that should be removed from the alias/copy/etc of this document when it is dropped
});
-
export const collectionSchema = createSchema({
childLayoutTemplate: Doc, // layout template to use to render children of a collecion
- childLayoutString: "string", //layout string to use to render children of a collection
+ childLayoutString: 'string', //layout string to use to render children of a collection
childClickedOpenTemplateView: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to read this value and apply template)
- childDontRegisterViews: "boolean", // whether views made of this document are registered so that they can be found when drawing links
+ childDontRegisterViews: 'boolean', // whether views made of this document are registered so that they can be found when drawing links
onChildClick: ScriptField, // script to run for each child when its clicked
onChildDoubleClick: ScriptField, // script to run for each child when its clicked
onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view
diff --git a/src/fields/util.ts b/src/fields/util.ts
index cbb560114..d87bb6656 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,41 +1,41 @@
+import { action, observable, runInAction, trace } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { DocServer } from '../client/DocServer';
+import { SerializationHelper } from '../client/util/SerializationHelper';
import { UndoManager } from '../client/util/UndoManager';
+import { returnZero } from '../Utils';
+import CursorField from './CursorField';
import {
- Doc,
- FieldResult,
- UpdatingFromServer,
- LayoutSym,
- AclPrivate,
+ AclAdmin,
+ AclAugment,
AclEdit,
+ AclPrivate,
AclReadonly,
- AclAugment,
+ AclSelfEdit,
AclSym,
+ AclUnset,
DataSym,
+ Doc,
DocListCast,
- AclAdmin,
- HeightSym,
- WidthSym,
- updateCachedAcls,
- AclUnset,
DocListCastAsync,
+ FieldResult,
ForceServerWrite,
+ HeightSym,
Initializing,
- AclSelfEdit,
+ LayoutSym,
+ updateCachedAcls,
+ UpdatingFromServer,
+ WidthSym,
} from './Doc';
-import { SerializationHelper } from '../client/util/SerializationHelper';
-import { ProxyField, PrefetchProxy } from './Proxy';
-import { RefField } from './RefField';
+import { Id, OnUpdate, Parent, Self, SelfProxy, Update } from './FieldSymbols';
+import { List } from './List';
import { ObjectField } from './ObjectField';
-import { action, observable, runInAction, trace } from 'mobx';
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from './FieldSymbols';
-import { DocServer } from '../client/DocServer';
+import { PrefetchProxy, ProxyField } from './Proxy';
+import { RefField } from './RefField';
+import { RichTextField } from './RichTextField';
+import { SchemaHeaderField } from './SchemaHeaderField';
import { ComputedField } from './ScriptField';
import { ScriptCast, StrCast } from './Types';
-import { returnZero } from '../Utils';
-import CursorField from './CursorField';
-import { List } from './List';
-import { SnappingManager } from '../client/util/SnappingManager';
-import { computedFn } from 'mobx-utils';
-import { RichTextField } from './RichTextField';
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -456,7 +456,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
? {
redo: action(() => {
diff.items.forEach((item: any) => {
- const ind = receiver[prop].indexOf(item.value ? item.value() : item);
+ const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ? item.value() : item);
ind !== -1 && receiver[prop].splice(ind, 1);
});
lastValue = ObjectField.MakeCopy(receiver[prop]);
@@ -464,8 +464,13 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
undo: () => {
// console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
diff.items.forEach((item: any) => {
- const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
+ if (item instanceof SchemaHeaderField) {
+ const ind = (prevValue as List<any>).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
+ ind !== -1 && receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && receiver[prop].splice(ind, 0, item);
+ } else {
+ const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
+ }
});
lastValue = ObjectField.MakeCopy(receiver[prop]);
},