diff options
Diffstat (limited to 'src/fields')
-rw-r--r-- | src/fields/Doc.ts | 3 | ||||
-rw-r--r-- | src/fields/List.ts | 72 | ||||
-rw-r--r-- | src/fields/ScriptField.ts | 176 | ||||
-rw-r--r-- | src/fields/URLField.ts | 69 | ||||
-rw-r--r-- | src/fields/documentSchemas.ts | 182 | ||||
-rw-r--r-- | src/fields/util.ts | 59 |
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]); }, |