@@ -585,6 +593,7 @@ export class SharingManager extends React.Component<{}> {
{Doc.noviceMode ? null : (
+ (this.overrideNested = !this.overrideNested))} checked={this.overrideNested} />
(this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} />
)}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 44af51341..422d2d6d7 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -190,36 +190,24 @@ export function ViewBoxAnnotatableComponent
()
}
const added = docs;
if (added.length) {
- const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {});
-
- GetEffectiveAcl(this.rootDoc) === AclAdmin &&
- aclKeys.forEach(key =>
+ Object.keys(Doc.GetProto(this.rootDoc)[DocAcl]) // apply all collection acls (except pseudo-acl 'Me') to each added doc
+ .filter(key => key !== 'acl-Me')
+ .forEach(key =>
added.forEach(d => {
- if (key != 'acl-Me') {
- const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]);
- const permissionSymbol = ReverseHierarchyMap.get(permissionString)?.acl;
- const permission = permissionSymbol && HierarchyMapping.get(permissionSymbol)?.name;
- distributeAcls(key, permission ?? SharingPermissions.Augment, Doc.GetProto(d));
- }
+ const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]);
+ const permissionSymbol = ReverseHierarchyMap.get(permissionString)?.acl;
+ const permission = permissionSymbol && HierarchyMapping.get(permissionSymbol)?.name;
+ distributeAcls(key, permission ?? SharingPermissions.Augment, d);
})
);
- if (effectiveAcl === AclAugment) {
+ if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) {
added.map(doc => {
+ doc._dragOnlyWithinContainer = undefined;
if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
- Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
- Doc.SetContainer(doc, targetDataDoc);
+ Doc.SetContainer(doc, this.rootDoc);
inheritParentAcls(targetDataDoc, doc);
});
- } else {
- added
- .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)))
- .map(doc => {
- doc._dragOnlyWithinContainer = undefined;
- if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
- Doc.SetContainer(doc, this.rootDoc);
- inheritParentAcls(targetDataDoc, doc);
- });
const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List;
if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index b513fe245..82d2bff56 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -50,18 +50,20 @@ export class PreviewCursor extends React.Component<{}> {
PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, PreviewCursor._addDocument).then(batch.end);
} else if (re.test(plain)) {
const url = plain;
- undoBatch(() =>
- PreviewCursor._addDocument(
- Docs.Create.WebDocument(url, {
- title: url,
- _width: 500,
- _height: 300,
- data_useCors: true,
- x: newPoint[0],
- y: newPoint[1],
- })
- )
- )();
+ if (url.startsWith(window.location.href)) {
+ undoBatch(() =>
+ PreviewCursor._addDocument(
+ Docs.Create.WebDocument(url, {
+ title: url,
+ _width: 500,
+ _height: 300,
+ data_useCors: true,
+ x: newPoint[0],
+ y: newPoint[1],
+ })
+ )
+ )();
+ } else alert('cannot paste dash into itself');
} else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) {
const clone = plain.startsWith('__DashCloneId(');
const docids = plain.split(':');
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 0daa3dd92..30bc8cbec 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -500,15 +500,15 @@ export class CollectionDockingView extends CollectionSubView() {
}
};
tabCreated = (tab: any) => {
- const aclKeys = Object.keys(Doc.GetProto(this.props.Document)[DocAcl] ?? {});
- aclKeys.forEach(key => {
- if (key != 'acl-Me') {
+ const aclKeys = Object.keys(Doc.GetProto(this.rootDoc)[DocAcl] ?? {});
+ aclKeys
+ .filter(key => key !== 'acl-Me')
+ .forEach(key => {
const permissionString = StrCast(Doc.GetProto(this.props.Document)[key]);
const permissionSymbol = ReverseHierarchyMap.get(permissionString)!.acl;
const permission = HierarchyMapping.get(permissionSymbol)!.name;
- distributeAcls(key, permission, Doc.GetProto(tab));
- }
- });
+ distributeAcls(key, permission, tab);
+ });
this.tabMap.add(tab);
tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
};
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 28fbdc192..8be295810 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -751,7 +751,9 @@ export namespace Doc {
}
};
const docAtKey = doc[key];
- if (docAtKey instanceof Doc) {
+ if (key === 'author') {
+ assignKey(Doc.CurrentUserEmail);
+ } else if (docAtKey instanceof Doc) {
if (pruneDocs.includes(docAtKey)) {
// prune doc and do nothing
} else if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || ['embedContainer', 'annotationOn', 'proto'].includes(key) || ((key === 'link_anchor_1' || key === 'link_anchor_2') && doc.author === Doc.CurrentUserEmail))) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 0e9940ced..815f3b186 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -65,7 +65,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
const writeToDoc =
sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Playground || writeMode === DocServer.WriteMode.LivePlayground || (effectiveAcl === AclAugment && value instanceof RichTextField);
- const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclAugment && Doc.CurrentUserEmail !== 'guest' && value instanceof RichTextField)) && !DocServer.Control.isReadOnly();
+ const writeToServer =
+ !DocServer.Control.isReadOnly() && //
+ (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclAugment && value instanceof RichTextField));
if (writeToDoc) {
if (value === undefined) {
@@ -230,52 +232,40 @@ function getEffectiveAcl(target: any, user?: string): symbol {
* @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection)
* inheritingFromCollection is not currently being used but could be used if acl assignment defaults change
*/
-export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean, visited?: Doc[]) {
+export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean) {
+ const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`;
if (!visited) visited = [] as Doc[];
- if (!target || visited.includes(target)) return;
- if ((target._type_collection === CollectionViewType.Docking && visited.length > 1) || Doc.GetProto(visited[0]) !== Doc.GetProto(target)) {
- if (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`) {
- target[key] = acl;
- }
- if (target !== Doc.GetProto(target)) {
- //apparently we can't call updateCachedAcls twice (once for the main dashboard, and again for the nested dashboard...???)
- updateCachedAcls(target);
- }
- //return;
- }
+ if (!target || visited.includes(target) || key === selfKey) return;
visited.push(target);
- let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
- // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive
- if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) {
- if (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`) {
- target[key] = acl;
- layoutDocChanged = true;
- }
- }
-
let dataDocChanged = false;
const dataDoc = target[DocData];
- if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) {
- if (GetEffectiveAcl(dataDoc) === AclAdmin && (target.author !== Doc.CurrentUserEmail || key !== `acl-${Doc.CurrentUserEmailNormalized}`)) {
- dataDoc[key] = acl;
- dataDocChanged = true;
- }
+ if (dataDoc && (allowUpgrade || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) {
+ // propagate ACLs to links, children, and annotations
- // maps over the links of the document
- LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
+ LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade));
- // maps over the children of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => {
- distributeAcls(key, acl, d, inheritingFromCollection, visited);
- d !== d[DocData] && distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited);
+ distributeAcls(key, acl, d, visited, allowUpgrade);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade);
});
- // maps over the annotations of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => {
- distributeAcls(key, acl, d, inheritingFromCollection, visited);
- d !== d[DocData] && distributeAcls(key, acl, d[DocData], inheritingFromCollection, visited);
+ distributeAcls(key, acl, d, visited, allowUpgrade);
+ d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade);
});
+
+ if (GetEffectiveAcl(dataDoc) === AclAdmin) {
+ dataDoc[key] = acl;
+ dataDocChanged = true;
+ }
+ }
+
+ let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
+ // if it is inheriting from a collection, it only inherits if A) allowUpgrade is set B) the key doesn't already exist or c) the right being inherited is more restrictive
+ if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) {
+ target[key] = acl;
+ layoutDocChanged = true;
}
layoutDocChanged && updateCachedAcls(target); // updates target[AclSym] when changes to acls have been made
--
cgit v1.2.3-70-g09d2
From a60fbbe02f0ff26ef04ce8b44695a82673164270 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Mon, 10 Jul 2023 09:32:52 -0400
Subject: updated how Lists and Docs get updated when they contain an
ObjectField that will be modified
---
src/client/DocServer.ts | 2 +-
src/client/views/ScriptingRepl.tsx | 3 +-
src/fields/CursorField.ts | 41 ++--
src/fields/Doc.ts | 21 +-
src/fields/DocSymbols.ts | 2 +-
src/fields/FieldSymbols.ts | 2 +-
src/fields/List.ts | 459 +++++++++++++++++--------------------
src/fields/ObjectField.ts | 15 +-
src/fields/SchemaHeaderField.ts | 14 +-
src/fields/util.ts | 196 +++++++++-------
10 files changed, 378 insertions(+), 377 deletions(-)
(limited to 'src/client/DocServer.ts')
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 67be96d13..5b452b95b 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -198,7 +198,7 @@ export namespace DocServer {
export namespace Control {
let _isReadOnly = false;
export function makeReadOnly() {
- if (!Control.isReadOnly()) {
+ if (!_isReadOnly) {
_isReadOnly = true;
_CreateField = field => (_cache[field[Id]] = field);
_UpdateField = emptyFunction;
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 5dfe10def..2bc2d5e6b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -5,6 +5,7 @@ import * as React from 'react';
import { DocumentManager } from '../util/DocumentManager';
import { CompileScript, Transformer, ts } from '../util/Scripting';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
+import { undoable } from '../util/UndoManager';
import { DocumentIconContainer } from './nodes/DocumentIcon';
import { OverlayView } from './OverlayView';
import './ScriptingRepl.scss';
@@ -161,7 +162,7 @@ export class ScriptingRepl extends React.Component {
this.commands.push({ command: this.commandString, result: script.errors });
return;
}
- const result = script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() }));
+ const result = undoable(() => script.run({ args: this.args }, e => this.commands.push({ command: this.commandString, result: e.toString() })), 'run:' + this.commandString)();
if (result.success) {
this.commands.push({ command: this.commandString, result: result.result });
this.commandsHistory.push(this.commandString);
diff --git a/src/fields/CursorField.ts b/src/fields/CursorField.ts
index a8a2859d2..46f5a8e1c 100644
--- a/src/fields/CursorField.ts
+++ b/src/fields/CursorField.ts
@@ -1,44 +1,43 @@
-import { ObjectField } from "./ObjectField";
-import { observable } from "mobx";
-import { Deserializable } from "../client/util/SerializationHelper";
-import { serializable, createSimpleSchema, object, date } from "serializr";
-import { OnUpdate, ToScriptString, ToString, Copy } from "./FieldSymbols";
+import { ObjectField } from './ObjectField';
+import { observable } from 'mobx';
+import { Deserializable } from '../client/util/SerializationHelper';
+import { serializable, createSimpleSchema, object, date } from 'serializr';
+import { FieldChanged, ToScriptString, ToString, Copy } from './FieldSymbols';
export type CursorPosition = {
- x: number,
- y: number
+ x: number;
+ y: number;
};
export type CursorMetadata = {
- id: string,
- identifier: string,
- timestamp: number
+ id: string;
+ identifier: string;
+ timestamp: number;
};
export type CursorData = {
- metadata: CursorMetadata,
- position: CursorPosition
+ metadata: CursorMetadata;
+ position: CursorPosition;
};
const PositionSchema = createSimpleSchema({
x: true,
- y: true
+ y: true,
});
const MetadataSchema = createSimpleSchema({
id: true,
identifier: true,
- timestamp: true
+ timestamp: true,
});
const CursorSchema = createSimpleSchema({
metadata: object(MetadataSchema),
- position: object(PositionSchema)
+ position: object(PositionSchema),
});
-@Deserializable("cursor")
+@Deserializable('cursor')
export default class CursorField extends ObjectField {
-
@serializable(object(CursorSchema))
readonly data: CursorData;
@@ -50,7 +49,7 @@ export default class CursorField extends ObjectField {
setPosition(position: CursorPosition) {
this.data.position = position;
this.data.metadata.timestamp = Date.now();
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
[Copy]() {
@@ -58,9 +57,9 @@ export default class CursorField extends ObjectField {
}
[ToScriptString]() {
- return "invalid";
+ return 'invalid';
}
[ToString]() {
- return "invalid";
+ return 'invalid';
}
-}
\ No newline at end of file
+}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 8be295810..698e09915 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -37,11 +37,10 @@ import {
Initializing,
Self,
SelfProxy,
- Update,
UpdatingFromServer,
Width,
} from './DocSymbols';
-import { Copy, HandleUpdate, Id, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols';
import { InkField, InkTool } from './InkField';
import { List, ListFieldName } from './List';
import { ObjectField } from './ObjectField';
@@ -52,7 +51,7 @@ import { listSpec } from './Schema';
import { ComputedField, ScriptField } from './ScriptField';
import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
-import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util';
+import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, containedFieldChangedHandler } from './util';
import JSZip = require('jszip');
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -337,9 +336,10 @@ export class Doc extends RefField {
for (const key in value) {
const field = value[key];
field !== undefined && (this[FieldKeys][key] = true);
- if (!(field instanceof ObjectField)) continue;
- field[Parent] = this[Self];
- field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ if (field instanceof ObjectField) {
+ field[Parent] = this[Self];
+ field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field);
+ }
}
}
@@ -360,12 +360,13 @@ export class Doc extends RefField {
private [ForceServerWrite]: boolean = false;
public [Initializing]: boolean = false;
- private [Update] = (diff: any) => {
- (!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff);
- };
-
private [Self] = this;
private [SelfProxy]: any;
+ public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
+ if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
+ DocServer.UpdateField(this[Id], serverOp);
+ }
+ };
public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
public [Width] = () => NumCast(this[SelfProxy]._width);
public [Height] = () => NumCast(this[SelfProxy]._height);
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 65decc147..eab26ed10 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -1,4 +1,4 @@
-export const Update = Symbol('DocUpdate');
+export const DocUpdated = Symbol('DocUpdated');
export const Self = Symbol('DocSelf');
export const SelfProxy = Symbol('DocSelfProxy');
export const FieldKeys = Symbol('DocFieldKeys');
diff --git a/src/fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts
index c381f14f5..0dbeb064b 100644
--- a/src/fields/FieldSymbols.ts
+++ b/src/fields/FieldSymbols.ts
@@ -1,6 +1,6 @@
export const HandleUpdate = Symbol('FieldHandleUpdate');
export const Id = Symbol('FieldId');
-export const OnUpdate = Symbol('FieldOnUpdate');
+export const FieldChanged = Symbol('FieldChanged');
export const Parent = Symbol('FieldParent');
export const Copy = Symbol('FieldCopy');
export const ToValue = Symbol('FieldToValue');
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 033fa569b..183d644d3 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -3,217 +3,15 @@ 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 { FieldTuples, Self, SelfProxy, Update } from './DocSymbols';
import { Field } from './Doc';
-import { Copy, OnUpdate, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { FieldTuples, Self, SelfProxy } from './DocSymbols';
+import { Copy, FieldChanged, Parent, ToScriptString, ToString } 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');
- },
- fill(value: any, start?: number, end?: number) {
- if (value instanceof RefField) {
- throw new Error('fill with RefFields not supported yet');
- }
- const res = this[Self].__fieldTuples.fill(value, start, end);
- this[Update]();
- return res;
- },
- pop(): any {
- const field = toRealField(this[Self].__fieldTuples.pop());
- this[Update]();
- return field;
- },
- push: action(function (this: any, ...items: any[]) {
- items = items.map(toObjectField);
-
- const list = this[Self];
- const length = list.__fieldTuples.length;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i + length, item, this);
- }
- }
- const res = list.__fieldTuples.push(...items);
- this[Update]({ op: '$addToSet', items, length: length + items.length });
- return res;
- }),
- reverse() {
- const res = this[Self].__fieldTuples.reverse();
- this[Update]();
- return res;
- },
- shift() {
- const res = toRealField(this[Self].__fieldTuples.shift());
- this[Update]();
- return res;
- },
- sort(cmpFunc: any) {
- this[Self].__realFields(); // coerce retrieving entire array
- const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
- this[Update]();
- return res;
- },
- splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
- this[Self].__realFields(); // coerce retrieving entire array
- items = items.map(toObjectField);
- const list = this[Self];
- const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- //TODO Need to change indices of other fields in array
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i + start, item, this);
- }
- }
- let hintArray: { val: any; index: number }[] = [];
- for (let i = start; i < start + deleteCount; i++) {
- hintArray.push({ val: list.__fieldTuples[i], index: i });
- }
- const res = list.__fieldTuples.splice(start, deleteCount, ...items);
- // the hint object sends the starting index of the slice and the number
- // of elements to delete.
- this[Update](
- items.length === 0 && deleteCount
- ? { op: '$remFromSet', items: removed, hint: { start: start, deleteCount: deleteCount }, length: list.__fieldTuples.length }
- : items.length && !deleteCount && start === list.__fieldTuples.length
- ? { op: '$addToSet', items, length: list.__fieldTuples.length }
- : undefined
- );
- return res.map(toRealField);
- }),
- unshift(...items: any[]) {
- items = items.map(toObjectField);
- const list = this[Self];
- const length = list.__fieldTuples.length;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- //TODO Error checking to make sure parent doesn't already exist
- //TODO Need to change indices of other fields in array
- if (item instanceof ObjectField) {
- item[Parent] = list;
- item[OnUpdate] = updateFunction(list, i, item, this);
- }
- }
- const res = this[Self].__fieldTuples.unshift(...items);
- this[Update]();
- return res;
- },
- /// Accessor methods
- concat: action(function (this: any, ...items: any[]) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.map(toRealField).concat(...items);
- }),
- includes(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().includes(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
- }
- },
- indexOf(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().indexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
- }
- },
- join(separator: any) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.map(toRealField).join(separator);
- },
- lastElement() {
- return this[Self].__realFields().lastElement();
- },
- lastIndexOf(valueToFind: any, fromIndex: number) {
- if (valueToFind instanceof RefField) {
- return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
- } else {
- return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
- }
- },
- slice(begin: number, end: number) {
- this[Self].__realFields();
- return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
- },
-
- /// Iteration methods
- entries() {
- return this[Self].__realFields().entries();
- },
- every(callback: any, thisArg: any) {
- return this[Self].__realFields().every(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- filter(callback: any, thisArg: any) {
- return this[Self].__realFields().filter(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- find(callback: any, thisArg: any) {
- return this[Self].__realFields().find(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- findIndex(callback: any, thisArg: any) {
- return this[Self].__realFields().findIndex(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- forEach(callback: any, thisArg: any) {
- return this[Self].__realFields().forEach(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- map(callback: any, thisArg: any) {
- return this[Self].__realFields().map(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- reduce(callback: any, initialValue: any) {
- return this[Self].__realFields().reduce(callback, initialValue);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
- },
- reduceRight(callback: any, initialValue: any) {
- return this[Self].__realFields().reduceRight(callback, initialValue);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
- },
- some(callback: any, thisArg: any) {
- return this[Self].__realFields().some(callback, thisArg);
- // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
- // If we don't want to support the array parameter, we should use this version instead
- // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
- },
- values() {
- return this[Self].__realFields().values();
- },
- [Symbol.iterator]() {
- return this[Self].__realFields().values();
- },
-};
+import { deleteProperty, getter, setter, containedFieldChangedHandler } from './util';
function toObjectField(field: Field) {
return field instanceof RefField ? new ProxyField(field) : field;
@@ -223,38 +21,221 @@ function toRealField(field: Field) {
return field instanceof ProxyField ? field.value : field;
}
-function listGetter(target: any, prop: string | symbol, receiver: any): any {
- if (listHandlers.hasOwnProperty(prop)) {
- return listHandlers[prop];
- }
- return getter(target, prop, receiver);
-}
-
-interface ListSpliceUpdate {
- type: 'splice';
- index: number;
- added: T[];
- removedCount: number;
-}
-
-interface ListIndexUpdate {
- type: 'update';
- index: number;
- newValue: T;
-}
-
-type ListUpdate = ListSpliceUpdate | ListIndexUpdate;
-
type StoredType = T extends RefField ? ProxyField : T;
export const ListFieldName = 'fields';
@Deserializable('list')
class ListImpl extends ObjectField {
+ static listHandlers: any = {
+ /// Mutator methods
+ copyWithin() {
+ 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');
+ }
+ const res = this[Self].__fieldTuples.fill(value, start, end);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ pop(): any {
+ const field = toRealField(this[Self].__fieldTuples.pop());
+ this[SelfProxy][FieldChanged]?.();
+ return field;
+ },
+ push: action(function (this: ListImpl, ...items: any[]) {
+ items = items.map(toObjectField);
+
+ const list = this[Self];
+ const length = list.__fieldTuples.length;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], i + length, item);
+ }
+ }
+ const res = list.__fieldTuples.push(...items);
+ this[SelfProxy][FieldChanged]?.({ op: '$addToSet', items, length: length + items.length });
+ return res;
+ }),
+ reverse() {
+ const res = this[Self].__fieldTuples.reverse();
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ shift() {
+ const res = toRealField(this[Self].__fieldTuples.shift());
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ sort(cmpFunc: any) {
+ this[Self].__realFields(); // coerce retrieving entire array
+ const res = this[Self].__fieldTuples.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+ this[Self].__realFields(); // coerce retrieving entire array
+ items = items.map(toObjectField);
+ const list = this[Self];
+ const removed = list.__fieldTuples.filter((item: any, i: number) => i >= start && i < start + deleteCount);
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this, i + start, item);
+ }
+ }
+ let hintArray: { val: any; index: number }[] = [];
+ for (let i = start; i < start + deleteCount; i++) {
+ hintArray.push({ val: list.__fieldTuples[i], index: i });
+ }
+ const res = list.__fieldTuples.splice(start, deleteCount, ...items);
+ // the hint object sends the starting index of the slice and the number
+ // of elements to delete.
+ this[SelfProxy][FieldChanged]?.(
+ items.length === 0 && deleteCount
+ ? { op: '$remFromSet', items: removed, hint: { start, deleteCount }, length: list.__fieldTuples.length }
+ : items.length && !deleteCount && start === list.__fieldTuples.length
+ ? { op: '$addToSet', items, length: list.__fieldTuples.length }
+ : undefined
+ );
+ return res.map(toRealField);
+ }),
+ unshift(...items: any[]) {
+ items = items.map(toObjectField);
+ const list = this[Self];
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[FieldChanged] = containedFieldChangedHandler(this, i, item);
+ }
+ }
+ const res = this[Self].__fieldTuples.unshift(...items);
+ this[SelfProxy][FieldChanged]?.();
+ return res;
+ },
+ /// Accessor methods
+ concat: action(function (this: any, ...items: any[]) {
+ this[Self].__realFields();
+ return this[Self].__fieldTuples.map(toRealField).concat(...items);
+ }),
+ includes(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields().includes(valueToFind, fromIndex);
+ } else {
+ return this[Self].__fieldTuples.includes(valueToFind, fromIndex);
+ }
+ },
+ indexOf(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields().indexOf(valueToFind, fromIndex);
+ }
+ return this[Self].__fieldTuples.indexOf(valueToFind, fromIndex);
+ },
+ join(separator: any) {
+ this[Self].__realFields();
+ return this[Self].__fieldTuples.map(toRealField).join(separator);
+ },
+ lastElement() {
+ return this[Self].__realFields().lastElement();
+ },
+ lastIndexOf(valueToFind: any, fromIndex: number) {
+ if (valueToFind instanceof RefField) {
+ return this[Self].__realFields().lastIndexOf(valueToFind, fromIndex);
+ } else {
+ return this[Self].__fieldTuples.lastIndexOf(valueToFind, fromIndex);
+ }
+ },
+ slice(begin: number, end: number) {
+ this[Self].__realFields();
+ return this[Self].__fieldTuples.slice(begin, end).map(toRealField);
+ },
+
+ /// Iteration methods
+ entries() {
+ return this[Self].__realFields().entries();
+ },
+ every(callback: any, thisArg: any) {
+ return this[Self].__realFields().every(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ filter(callback: any, thisArg: any) {
+ return this[Self].__realFields().filter(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ find(callback: any, thisArg: any) {
+ return this[Self].__realFields().find(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ findIndex(callback: any, thisArg: any) {
+ return this[Self].__realFields().findIndex(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ forEach(callback: any, thisArg: any) {
+ return this[Self].__realFields().forEach(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ map(callback: any, thisArg: any) {
+ return this[Self].__realFields().map(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ reduce(callback: any, initialValue: any) {
+ return this[Self].__realFields().reduce(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ reduceRight(callback: any, initialValue: any) {
+ return this[Self].__realFields().reduceRight(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ some(callback: any, thisArg: any) {
+ return this[Self].__realFields().some(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fieldTuples.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ values() {
+ return this[Self].__realFields().values();
+ },
+ [Symbol.iterator]() {
+ return this[Self].__realFields().values();
+ },
+ };
+ static listGetter(target: any, prop: string | symbol, receiver: any): any {
+ if (ListImpl.listHandlers.hasOwnProperty(prop)) {
+ return ListImpl.listHandlers[prop];
+ }
+ return getter(target, prop, receiver);
+ }
constructor(fields?: T[]) {
super();
const list = new Proxy(this, {
set: setter,
- get: listGetter,
+ get: ListImpl.listGetter,
ownKeys: target => Object.keys(target.__fieldTuples),
getOwnPropertyDescriptor: (target, prop) => {
if (prop in target[FieldTuples]) {
@@ -270,9 +251,9 @@ class ListImpl extends ObjectField {
throw new Error("Currently properties can't be defined on documents using Object.defineProperty");
},
});
- this[SelfProxy] = list;
+ this[SelfProxy] = list as any as List; // bcz: ugh .. don't know how to convince typesecript that list is a List
if (fields) {
- (list as any).push(...fields);
+ this[SelfProxy].push(...fields);
}
return list;
}
@@ -305,10 +286,10 @@ class ListImpl extends ObjectField {
private set __fieldTuples(value) {
this[FieldTuples] = value;
for (const key in value) {
- const field = value[key];
- if (field instanceof ObjectField) {
- field[Parent] = this[Self];
- field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ const item = value[key];
+ if (item instanceof ObjectField) {
+ item[Parent] = this[Self];
+ item[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], Number(key), item);
}
}
}
@@ -322,16 +303,8 @@ class ListImpl extends ObjectField {
// @serializable(alias("fields", list(autoObject())))
@observable
private [FieldTuples]: StoredType[] = [];
-
- private [Update] = (diff: any) => {
- // console.log(diff);
- const update = this[OnUpdate];
- // update && update(diff);
- update?.(diff);
- };
-
private [Self] = this;
- private [SelfProxy]: any;
+ private [SelfProxy]: List; // also used in utils.ts even though it won't be found using find all references
[ToScriptString]() {
return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts
index daa8a7777..b5bc2952a 100644
--- a/src/fields/ObjectField.ts
+++ b/src/fields/ObjectField.ts
@@ -1,9 +1,14 @@
-import { RefField } from "./RefField";
-import { OnUpdate, Parent, Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { ScriptingGlobals } from "../client/util/ScriptingGlobals";
+import { RefField } from './RefField';
+import { FieldChanged, Parent, Copy, ToScriptString, ToString } from './FieldSymbols';
+import { ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { Field } from './Doc';
export abstract class ObjectField {
- public [OnUpdate]?: (diff?: any) => void;
+ // prettier-ignore
+ public [FieldChanged]?: (diff?: { op: '$addToSet' | '$remFromSet' | '$set';
+ items: Field[] | undefined;
+ length: number | undefined;
+ hint?: any }, serverOp?: any) => void;
public [Parent]?: RefField | ObjectField;
abstract [Copy](): ObjectField;
@@ -17,4 +22,4 @@ export namespace ObjectField {
}
}
-ScriptingGlobals.add(ObjectField);
\ No newline at end of file
+ScriptingGlobals.add(ObjectField);
diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts
index 4b1855cb0..6dde2e5aa 100644
--- a/src/fields/SchemaHeaderField.ts
+++ b/src/fields/SchemaHeaderField.ts
@@ -1,7 +1,7 @@
import { Deserializable } from '../client/util/SerializationHelper';
import { serializable, primitive } from 'serializr';
import { ObjectField } from './ObjectField';
-import { Copy, ToScriptString, ToString, OnUpdate } from './FieldSymbols';
+import { Copy, ToScriptString, ToString, FieldChanged } from './FieldSymbols';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView';
@@ -82,32 +82,32 @@ export class SchemaHeaderField extends ObjectField {
setHeading(heading: string) {
this.heading = heading;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setColor(color: string) {
this.color = color;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setType(type: ColumnType) {
this.type = type;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setWidth(width: number) {
this.width = width;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setDesc(desc: boolean | undefined) {
this.desc = desc;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
setCollapsed(collapsed: boolean | undefined) {
this.collapsed = collapsed;
- this[OnUpdate]?.();
+ this[FieldChanged]?.();
}
[Copy]() {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 36f619120..4dcbf1fbe 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,15 +1,13 @@
import { $mobx, action, observable, runInAction, trace } from 'mobx';
import { computedFn } from 'mobx-utils';
import { DocServer } from '../client/DocServer';
-import { CollectionViewType } from '../client/documents/DocumentTypes';
import { LinkManager } from '../client/util/LinkManager';
import { SerializationHelper } from '../client/util/SerializationHelper';
import { UndoManager } from '../client/util/UndoManager';
import { returnZero } from '../Utils';
-import CursorField from './CursorField';
-import { aclLevel, Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc';
-import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclSelfEdit, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, Update, UpdatingFromServer, Width } from './DocSymbols';
-import { Id, OnUpdate, Parent, ToValue } from './FieldSymbols';
+import { aclLevel, Doc, DocListCast, Field, FieldResult, HierarchyMapping, ReverseHierarchyMap, StrListCast, updateCachedAcls } from './Doc';
+import { AclAdmin, AclAugment, AclEdit, AclPrivate, DocAcl, DocData, DocLayout, FieldKeys, ForceServerWrite, Height, Initializing, SelfProxy, UpdatingFromServer, Width } from './DocSymbols';
+import { FieldChanged, Id, Parent, ToValue } from './FieldSymbols';
import { List } from './List';
import { ObjectField } from './ObjectField';
import { PrefetchProxy, ProxyField } from './Proxy';
@@ -51,11 +49,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
throw new Error("Can't put the same object in multiple documents at the same time");
}
value[Parent] = receiver;
- value[OnUpdate] = updateFunction(target, prop, value, receiver);
+ value[FieldChanged] = containedFieldChangedHandler(receiver, prop, value);
}
if (curValue instanceof ObjectField) {
delete curValue[Parent];
- delete curValue[OnUpdate];
+ delete curValue[FieldChanged];
}
const effectiveAcl = GetEffectiveAcl(target);
@@ -81,8 +79,10 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
}
if (writeToServer) {
- if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } });
- else target[Update]({ $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : value === undefined ? null : value } });
+ // prettier-ignore
+ if (value === undefined)
+ (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $unset: { ['fields.' + prop]: '' } });
+ else (target as Doc|ObjectField)[FieldChanged]?.(undefined, { $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) :value}});
if (prop === 'author' || prop.toString().startsWith('acl')) updateCachedAcls(target);
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
@@ -229,7 +229,8 @@ function getEffectiveAcl(target: any, user?: string): symbol {
* @param key the key storing the access right (e.g. acl-groupname)
* @param acl the access right being stored (e.g. "Can Edit")
* @param target the document on which this access right is being set
- * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection)
+ * @param visited list of Doc's already distributed to.
+ * @param allowUpgrade whether permissions can be made less restrictive
* inheritingFromCollection is not currently being used but could be used if acl assignment defaults change
*/
export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean) {
@@ -274,6 +275,9 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
dataDocChanged && updateCachedAcls(dataDoc);
}
+//
+// target should be either a Doc or ListImpl. receiver should be a Proxy Or List.
+//
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
let prop = in_prop;
const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop);
@@ -344,86 +348,104 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
return true;
}
-export function updateFunction(target: any, prop: any, value: any, receiver: any) {
- let lastValue = ObjectField.MakeCopy(value);
- return (diff?: any) => {
- const op =
- diff?.op === '$addToSet'
- ? { $addToSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List(diff.items)) } }
- : diff?.op === '$remFromSet'
- ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List(diff.items)), hint: diff.hint } }
- : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(value) } };
- !op.$set && ((op as any).length = diff.length);
- const prevValue = ObjectField.MakeCopy(lastValue as List);
- lastValue = ObjectField.MakeCopy(value);
- const newValue = ObjectField.MakeCopy(value);
-
- if (!(value instanceof CursorField) && !value?.some?.((v: any) => v instanceof CursorField)) {
- !receiver[UpdatingFromServer] &&
+// this function creates a function that can be used to setup Undo for whenever an ObjectField changes.
+// the idea is that the Doc field setter can only setup undo at the granularity of an entire field and won't even be called if
+// just a part of a field (eg. field within an ObjectField) changes. This function returns a function that can be called
+// whenever an internal ObjectField field changes. It should be passed a 'diff' specification describing the change. Currently,
+// List's are the only true ObjectFields that can be partially modified (ignoring SchemaHeaderFields which should go away).
+// The 'diff' specification that a list can send is limited to indicating that something was added, removed, or that the list contents
+// were replaced. Based on this specification, an Undo event is setup that will save enough information about the ObjectField to be
+// able to undo and redo the partial change.
+//
+export function containedFieldChangedHandler(container: List | Doc, prop: string | number, liveContainedField: ObjectField) {
+ let lastValue: FieldResult = liveContainedField instanceof ObjectField ? ObjectField.MakeCopy(liveContainedField) : liveContainedField;
+ return (diff?: { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, dummyServerOp?: any) => {
+ const serializeItems = () => ({ __type: 'list', fields: diff?.items?.map((item: Field) => SerializationHelper.Serialize(item)) });
+ // prettier-ignore
+ const serverOp = diff?.op === '$addToSet'
+ ? { $addToSet: { ['fields.' + prop]: serializeItems() }, length: diff.length }
+ : diff?.op === '$remFromSet'
+ ? { $remFromSet: { ['fields.' + prop]: serializeItems(), hint: diff.hint}, length: diff.length }
+ : { $set: { ['fields.' + prop]: liveContainedField ? SerializationHelper.Serialize(liveContainedField) : undefined } };
+
+ if (!(container instanceof Doc) || !container[UpdatingFromServer]) {
+ const prevValue = ObjectField.MakeCopy(lastValue as List);
+ lastValue = ObjectField.MakeCopy(liveContainedField);
+ const newValue = ObjectField.MakeCopy(liveContainedField);
+ if (diff?.op === '$addToSet') {
UndoManager.AddEvent(
- diff?.op === '$addToSet'
- ? {
- redo: () => {
- console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
- receiver[prop].push(...diff.items.map((item: any) => item.value ?? item));
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: action(() => {
- // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- if (item instanceof SchemaHeaderField) {
- const ind = receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
- ind !== -1 && receiver[prop].splice(ind, 1);
- } else {
- const ind = receiver[prop].indexOf(item.value ?? item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- }
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- prop: 'add ' + diff.items.length + ' items to list',
- }
- : diff?.op === '$remFromSet'
- ? {
- redo: action(() => {
- console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ?? item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- undo: () => {
- // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- if (item instanceof SchemaHeaderField) {
- const ind = (prevValue as List).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).indexOf(item.value ?? item);
- ind !== -1 && receiver[prop].indexOf(item.value ?? item) === -1 && receiver[prop].splice(ind, 0, item);
- }
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: 'remove ' + diff.items.length + ' items from list',
- }
- : {
- redo: () => {
- console.log('redo list: ' + prop, receiver[prop]); // bcz: uncomment to log undo
- receiver[prop] = ObjectField.MakeCopy(newValue as List);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: () => {
- // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo
- receiver[prop] = ObjectField.MakeCopy(prevValue as List);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: 'assign list',
- },
+ {
+ redo: () => {
+ //console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo
+ (container as any)[prop as any]?.push(...(diff.items || [])?.map((item: any) => item.value ?? item));
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ undo: action(() => {
+ // console.log('undo $add: ' + prop, diff.items); // bcz: uncomment to log undo
+ diff.items?.forEach((item: any) => {
+ const ind =
+ item instanceof SchemaHeaderField //
+ ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading)
+ : (container as any)[prop as any]?.indexOf(item.value ?? item);
+ ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ }),
+ prop: 'add ' + diff.items?.length + ' items to list',
+ },
+ diff?.items
+ );
+ } else if (diff?.op === '$remFromSet') {
+ UndoManager.AddEvent(
+ {
+ redo: action(() => {
+ // console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
+ diff.items?.forEach((item: any) => {
+ const ind =
+ item instanceof SchemaHeaderField //
+ ? (container as any)[prop as any]?.findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading)
+ : (container as any)[prop as any]?.indexOf(item.value ?? item);
+ ind !== undefined && ind !== -1 && (container as any)[prop as any]?.splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ }),
+ undo: () => {
+ // console.log('undo $rem: ' + prop, diff.items); // bcz: uncomment to log undo
+ diff.items?.forEach((item: any) => {
+ if (item instanceof SchemaHeaderField) {
+ const ind = (prevValue as List).findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading);
+ ind !== -1 && (container as any)[prop as any].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) === -1 && (container as any)[prop as any].splice(ind, 0, item);
+ } else {
+ const ind = (prevValue as List).indexOf(item.value ?? item);
+ ind !== -1 && (container as any)[prop as any].indexOf(item.value ?? item) === -1 && (container as any)[prop as any].splice(ind, 0, item);
+ }
+ });
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ prop: 'remove ' + diff.items?.length + ' items from list',
+ },
diff?.items
);
+ } else {
+ const setFieldVal = (val: Field | undefined) => (container instanceof Doc ? (container[prop as string] = val) : (container[prop as number] = val as Field));
+ UndoManager.AddEvent(
+ {
+ redo: () => {
+ // console.log('redo list: ' + prop, fieldVal()); // bcz: uncomment to log undo
+ setFieldVal(newValue instanceof ObjectField ? ObjectField.MakeCopy(newValue) : undefined);
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ undo: () => {
+ // console.log('undo list: ' + prop, fieldVal()); // bcz: uncomment to log undo
+ setFieldVal(prevValue instanceof ObjectField ? ObjectField.MakeCopy(prevValue) : undefined);
+ lastValue = ObjectField.MakeCopy((container as any)[prop as any]);
+ },
+ prop: 'set list field',
+ },
+ diff?.items
+ );
+ }
}
- target[Update](op);
+ container[FieldChanged]?.(undefined, serverOp);
};
}
--
cgit v1.2.3-70-g09d2
From f93dabed48b4ebd3c7165ee28405c86bb65f2be9 Mon Sep 17 00:00:00 2001
From: bobzel
Date: Tue, 11 Jul 2023 16:31:29 -0400
Subject: better way to make 'guest' not write to server.
---
src/client/DocServer.ts | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
(limited to 'src/client/DocServer.ts')
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 5b452b95b..fa1fca6ff 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -207,7 +207,7 @@ export namespace DocServer {
}
export function makeEditable() {
- if (Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest') {
+ if (Control.isReadOnly()) {
location.reload();
// _isReadOnly = false;
// _CreateField = _CreateFieldImpl;
@@ -218,7 +218,7 @@ export namespace DocServer {
}
export function isReadOnly() {
- return _isReadOnly || Doc.CurrentUserEmail === 'guest';
+ return _isReadOnly;
}
}
@@ -483,7 +483,7 @@ export namespace DocServer {
function _CreateFieldImpl(field: RefField) {
_cache[field[Id]] = field;
const initialState = SerializationHelper.Serialize(field);
- Utils.Emit(_socket, MessageStore.CreateField, initialState);
+ Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.CreateField, initialState);
}
let _CreateField: (field: RefField) => void = errorFunc;
@@ -503,7 +503,7 @@ export namespace DocServer {
}
function _UpdateFieldImpl(id: string, diff: any) {
- !DocServer.Control.isReadOnly() && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ !DocServer.Control.isReadOnly() && Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
}
let _UpdateField: (id: string, diff: any) => void = errorFunc;
@@ -540,11 +540,11 @@ export namespace DocServer {
}
export function DeleteDocument(id: string) {
- Utils.Emit(_socket, MessageStore.DeleteField, id);
+ Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.DeleteField, id);
}
export function DeleteDocuments(ids: string[]) {
- Utils.Emit(_socket, MessageStore.DeleteFields, ids);
+ Doc.CurrentUserEmail !== 'guest' && Utils.Emit(_socket, MessageStore.DeleteFields, ids);
}
function _respondToDeleteImpl(ids: string | string[]) {
--
cgit v1.2.3-70-g09d2