Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions dash/dash-renderer/src/actions/callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {createAction, Action} from 'redux-actions';
import {addHttpHeaders} from '../actions';
import {notifyObservers, updateProps} from './index';
import {CallbackJobPayload} from '../reducers/callbackJobs';
import {handlePatch, isPatch} from './patch';
import {handlePatch, isPatch, parsePatchProps} from './patch';
import {computePaths, getPath} from './paths';

import {requestDependencies} from './requestDependencies';
Expand Down Expand Up @@ -419,22 +419,31 @@ function sideUpdate(outputs: SideUpdateOutput, cb: ICallbackPayload) {
}, [] as any[])
.forEach(([id, idProps]) => {
const state = getState();
dispatch(updateComponent(id, idProps, cb));

const componentPath = getPath(state.paths, id);
let oldComponent = {props: {}};
if (componentPath) {
oldComponent = getComponentLayout(componentPath, state);
}

const oldProps = oldComponent?.props || {};

const patchedProps = parsePatchProps(idProps, oldProps);

dispatch(updateComponent(id, patchedProps, cb));

if (!componentPath) {
// Component doesn't exist, doesn't matter just allow the
// callback to continue.
return;
}
const oldComponent = getComponentLayout(componentPath, state);

dispatch(
setPaths(
computePaths(
{
...oldComponent,
props: {...oldComponent.props, ...idProps}
props: {...oldComponent.props, ...patchedProps}
},
[...componentPath],
state.paths,
Expand Down
160 changes: 160 additions & 0 deletions dash/dash-renderer/src/actions/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,143 @@ function getLocationPath(location: LocationIndex[], obj: any) {
return current;
}

export class PatchBuilder {
private operations: PatchOperation[] = [];

assign(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Assign',
location,
params: {value}
});
return this;
}

merge(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Merge',
location,
params: {value}
});
return this;
}

extend(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Extend',
location,
params: {value}
});
return this;
}

delete(location: LocationIndex[]) {
this.operations.push({
operation: 'Delete',
location,
params: {}
});
return this;
}

insert(location: LocationIndex[], index: number, value: any) {
this.operations.push({
operation: 'Insert',
location,
params: {index, value}
});
return this;
}

append(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Append',
location,
params: {value}
});
return this;
}

prepend(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Prepend',
location,
params: {value}
});
return this;
}

add(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Add',
location,
params: {value}
});
return this;
}

sub(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Sub',
location,
params: {value}
});
return this;
}

mul(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Mul',
location,
params: {value}
});
return this;
}

div(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Div',
location,
params: {value}
});
return this;
}

clear(location: LocationIndex[]) {
this.operations.push({
operation: 'Clear',
location,
params: {}
});
return this;
}

reverse(location: LocationIndex[]) {
this.operations.push({
operation: 'Reverse',
location,
params: {}
});
return this;
}

remove(location: LocationIndex[], value: any) {
this.operations.push({
operation: 'Remove',
location,
params: {value}
});
return this;
}

build() {
return {
__dash_patch_update: true,
operations: this.operations
};
}
}

const patchHandlers: {[k: string]: PatchHandler} = {
Assign: (previous, patchOperation) => {
const {params, location} = patchOperation;
Expand Down Expand Up @@ -166,3 +303,26 @@ export function handlePatch<T>(previousValue: T, patchValue: any): T {

return reducedValue;
}

export function parsePatchProps(props: any, previousProps: any): {} {
if (!is(Object, props)) {
return props;
}

const patchedProps: any = {};

for (const key of Object.keys(props)) {
const val = props[key];
if (isPatch(val)) {
const previousValue = previousProps[key];
if (previousValue === undefined) {
throw new Error('Cannot patch undefined');
}
patchedProps[key] = handlePatch(previousValue, val);
} else {
patchedProps[key] = val;
}
}

return patchedProps;
}
16 changes: 15 additions & 1 deletion dash/dash-renderer/src/utils/clientsideFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {updateProps, notifyObservers, setPaths} from '../actions/index';
import {parsePatchProps, PatchBuilder} from '../actions/patch';
import {computePaths, getPath} from '../actions/paths';
import {getComponentLayout} from '../wrapper/wrapping';
import {getStores} from './stores';
Expand All @@ -23,6 +24,12 @@ function set_props(
} else {
componentPath = idOrPath;
}
const oldComponent = getComponentLayout(componentPath, state);

// Handle any patch props
props = parsePatchProps(props, oldComponent?.props || {});

// Update the props
dispatch(
updateProps({
props,
Expand All @@ -31,7 +38,13 @@ function set_props(
})
);
dispatch(notifyObservers({id: idOrPath, props}));
const oldComponent = getComponentLayout(componentPath, state);

if (!oldComponent) {
// console.error(
// `Could not find component with id or path: ${idOrPath}`
// );
return;
}

dispatch(
setPaths(
Expand Down Expand Up @@ -77,3 +90,4 @@ const dc = ((window as any).dash_clientside =
(window as any).dash_clientside || {});
dc['set_props'] = set_props;
dc['clean_url'] = dc['clean_url'] === undefined ? clean_url : dc['clean_url'];
dc['Patch'] = PatchBuilder;
Loading