-
Notifications
You must be signed in to change notification settings - Fork 14
Merge updates to Stratus - support live editing #835
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -342,6 +342,88 @@ export class Model<T = LooseObject> extends ModelBase<T> { | |
| return sanitizedOptions | ||
| } | ||
|
|
||
| getReadOnlyFields(): string[] { | ||
| const fields = this.meta.get('readOnlyFields') | ||
| return isArray(fields) ? fields.filter((field: any) => isString(field) && !!field) : [] | ||
| } | ||
|
|
||
| deletePath(obj: any, path: string): void { | ||
| if (!obj || !isString(path)) { | ||
| return | ||
| } | ||
|
|
||
| const parts = path.split('.') | ||
| const lastKey = parts.pop() | ||
| if (!lastKey) { | ||
| return | ||
| } | ||
|
|
||
| let parent = obj as Record<string, any> | ||
|
|
||
| for (const part of parts) { | ||
| if (!parent || !isObject(parent) || !has(parent, part)) { | ||
| parent = null | ||
| break | ||
| } | ||
| parent = parent[part] | ||
| } | ||
|
|
||
| if (parent && isObject(parent) && has(parent, lastKey)) { | ||
| delete parent[lastKey] | ||
| } | ||
| } | ||
|
|
||
| pruneEmptyBranches(obj: any): boolean { | ||
| if (!isObject(obj) || isArray(obj)) { | ||
| return isEmpty(obj) | ||
| } | ||
|
|
||
| const target = obj as Record<string, any> | ||
|
|
||
| for (const key of Object.keys(target)) { | ||
| const value = target[key] | ||
|
|
||
| if (isObject(value) && !isArray(value)) { | ||
| const emptyChild = this.pruneEmptyBranches(value) | ||
| if (emptyChild) { | ||
| delete target[key] | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| if (isUndefined(value)) { | ||
| delete target[key] | ||
| } | ||
| } | ||
|
|
||
| return isEmpty(target) | ||
| } | ||
|
|
||
| sanitizeReadOnlyPatchPayload(payload: any): any { | ||
| if (!isObject(payload)) { | ||
| return payload | ||
| } | ||
|
|
||
| const sanitized = cloneDeep(payload) | ||
| const readOnlyFields = this.getReadOnlyFields() | ||
|
|
||
| if (!readOnlyFields.length) { | ||
| return sanitized | ||
| } | ||
|
|
||
| forEach(readOnlyFields, (path: string) => { | ||
| this.deletePath(sanitized, path) | ||
| }) | ||
|
|
||
| this.pruneEmptyBranches(sanitized) | ||
|
|
||
| return sanitized | ||
| } | ||
|
|
||
| getSavablePatch(): any { | ||
| return this.sanitizeReadOnlyPatchPayload(cloneDeep(this.toPatch())) | ||
| } | ||
|
|
||
| // Watch for Data Changes | ||
| async watcher() { | ||
| // Ensure we only watch once | ||
|
|
@@ -377,6 +459,13 @@ export class Model<T = LooseObject> extends ModelBase<T> { | |
| const isUserChangeSet = isUndefined(changeSet) | ||
| if (isUserChangeSet) { | ||
| changeSet = super.handleChanges() | ||
|
|
||
| // Remove client-computed / read-only fields before autosave logic reacts | ||
| changeSet = this.sanitizeReadOnlyPatchPayload(changeSet) | ||
|
|
||
| if (!isEmpty(this.patch)) { | ||
| this.patch = this.sanitizeReadOnlyPatchPayload(this.patch) | ||
|
Comment on lines
+463
to
+467
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the recommended new lines of code please? |
||
| } | ||
| } | ||
|
|
||
| // Ensure ChangeSet is valid | ||
|
|
@@ -491,6 +580,8 @@ export class Model<T = LooseObject> extends ModelBase<T> { | |
| // XHR Flags for Collection | ||
| if (this.collection) { | ||
| // TODO: Change to a Model ID Register | ||
| // In live edit mode when we are editing a model, we don't necessarily want to make this collection be | ||
| // considered pending if there are child elements... but there may be contexts in which that is necessary | ||
| this.collection.pending = true | ||
|
|
||
| // Dispatch Collection Change Event | ||
|
|
@@ -790,7 +881,7 @@ export class Model<T = LooseObject> extends ModelBase<T> { | |
| return this.doSave(options) | ||
| } | ||
| // Sanity Checks for Persisted Entities | ||
| if (!this.isNew() && (this.pending || !this.completed || isEmpty(this.toPatch()))) { | ||
| if (!this.isNew() && (this.pending || !this.completed || isEmpty(this.getSavablePatch()))) { | ||
| console.warn( | ||
| `Blocked attempt to save ${isEmpty(this.toPatch()) ? 'an empty payload' : 'a duplicate XHR'} to a persisted model.` | ||
| ) | ||
|
|
@@ -845,7 +936,7 @@ export class Model<T = LooseObject> extends ModelBase<T> { | |
| if (this.autoSaveTimeout) { | ||
| clearTimeout(this.autoSaveTimeout) | ||
| } | ||
| if (this.pending || !this.completed || this.isNew() || isEmpty(this.toPatch())) { | ||
| if (this.pending || !this.completed || this.isNew() || isEmpty(this.getSavablePatch())) { | ||
| return | ||
| } | ||
| if (this.autoSaveHalt && !this.autoSave) { | ||
|
|
@@ -882,6 +973,9 @@ export class Model<T = LooseObject> extends ModelBase<T> { | |
| } | ||
| options.patch = (options.patch && !this.isNew()) | ||
| let data = super.toJSON(options) | ||
| if (options.patch) { | ||
| data = this.sanitizeReadOnlyPatchPayload(data) | ||
| } | ||
| const metaData = this.meta.get('api') | ||
| if (metaData) { | ||
| data = { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The traversal mixes lodash path-aware checks (
has(parent, part)) with direct property access (parent = parent[part]), which breaks for bracket-style segments such asitems[0]. In those cases the guard passes butparent[part]isundefined, so the read-only field is not removed and can still be sent in patch payloads when array-indexed read-only paths are configured.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the recommended new code block?