Skip to content

Commit

Permalink
Merge pull request #524 from cocopon/fix-blade-state
Browse files Browse the repository at this point in the history
Update blade state structure
  • Loading branch information
cocopon committed Mar 18, 2023
2 parents 32e8737 + 2e9df58 commit 058998c
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 49 deletions.
34 changes: 24 additions & 10 deletions packages/core/src/blade/binding/controller/binding-test.ts
Expand Up @@ -24,14 +24,15 @@ import {InputBindingController} from './input-binding';
function createController(
doc: Document,
config: {
key: string;
tag?: string;
value: number;
},
) {
const obj = {foo: config.value};
const obj = {[config.key]: config.value};
const binding = new ReadWriteBinding({
reader: colorFromRgbNumber,
target: new BindingTarget(obj, 'foo'),
target: new BindingTarget(obj, config.key),
writer: createColorNumberWriter(false),
});
const value = new InputBindingValue(
Expand Down Expand Up @@ -69,6 +70,7 @@ describe(BindingController.name, () => {
valueController: vc,
controller: bc,
} = createController(doc, {
key: 'foo',
value: 0x112233,
});

Expand All @@ -79,32 +81,44 @@ describe(BindingController.name, () => {
it('should export state', () => {
const doc = createTestWindow().document;
const {controller: c} = createController(doc, {
tag: 'foo',
key: 'foo',
tag: 'bar',
value: 0x112233,
});
const state = c.exportState();

assert.strictEqual(state.tag, 'foo');
assert.strictEqual(state.value, 0x112233);
assert.deepStrictEqual(c.exportState(), {
binding: {
key: 'foo',
value: 0x112233,
},
disabled: false,
hidden: false,
label: 'foo',
tag: 'bar',
});
});

it('should import state', () => {
const doc = createTestWindow().document;
const {controller: c} = createController(doc, {
tag: 'foo',
key: 'foo',
tag: 'bar',
value: 0x112233,
});

assert.strictEqual(
c.importState({
binding: {
key: 'foo',
value: 0,
},
disabled: true,
hidden: true,
label: 'label',
tag: 'bar',
value: 0,
tag: 'baz',
}),
true,
);
assert.strictEqual(c.tag, 'bar');
assert.strictEqual(c.tag, 'baz');
});
});
8 changes: 5 additions & 3 deletions packages/core/src/blade/binding/controller/binding.ts
Expand Up @@ -67,10 +67,12 @@ export class BindingController<
}

override exportState(): BladeState {
return exportBladeState(() => super.exportState(), {
key: this.value.binding.target.key,
return exportBladeState(() => excludeValue(super.exportState()), {
binding: {
key: this.value.binding.target.key,
value: this.value.binding.target.read(),
},
tag: this.tag,
value: this.value.binding.target.read(),
});
}
}
Expand Down
Expand Up @@ -129,10 +129,12 @@ describe(InputBindingController.name, () => {

assert.strictEqual(
bc.importState({
binding: {
value: 0x445566,
},
disabled: true,
hidden: true,
label: 'bar',
value: 0x445566,
}),
true,
);
Expand All @@ -148,10 +150,12 @@ describe(InputBindingController.name, () => {

assert.strictEqual(
bc.importState({
binding: {
value: {x: 3, y: 4},
},
disabled: true,
hidden: true,
label: 'bar',
value: {x: 3, y: 4},
}),
true,
);
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/blade/binding/controller/input-binding.ts
Expand Up @@ -23,10 +23,12 @@ export class InputBindingController<
state,
(s) => super.importState(s),
(p) => ({
value: p.required.raw,
binding: p.required.object({
value: p.required.raw,
}),
}),
(result) => {
this.value.binding.inject(result.value);
this.value.binding.inject(result.binding.value);
this.value.fetch();
return true;
},
Expand Down
23 changes: 16 additions & 7 deletions packages/core/src/blade/binding/controller/monitor-binding-test.ts
Expand Up @@ -21,13 +21,14 @@ function createController(
doc: Document,
config: {
tag?: string;
key: string;
value: number;
},
) {
const obj = {foo: config.value};
const obj = {[config.key]: config.value};
const binding = new ReadonlyBinding({
reader: numberFromUnknown,
target: new BindingTarget(obj, 'foo'),
target: new BindingTarget(obj, config.key),
});
const value = new MonitorBindingValue({
binding: binding,
Expand All @@ -54,13 +55,21 @@ describe(MonitorBindingController.name, () => {
it('should export state', () => {
const doc = createTestWindow().document;
const c = createController(doc, {
tag: 'foo',
key: 'foo',
tag: 'bar',
value: 123,
});
const state = c.exportState();

assert.strictEqual(state.readonly, true);
assert.strictEqual(state.tag, 'foo');
assert.strictEqual(state.value, 123);
assert.deepStrictEqual(c.exportState(), {
binding: {
key: 'foo',
readonly: true,
value: 123,
},
disabled: false,
hidden: false,
label: 'foo',
tag: 'bar',
});
});
});
Expand Up @@ -27,7 +27,9 @@ export class MonitorBindingController<
> extends BindingController<TpBuffer<T>, Vc, MonitorBindingValue<T>> {
override exportState(): BladeState {
return exportBladeState(() => super.exportState(), {
readonly: true,
binding: {
readonly: true,
},
});
}
}
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/blade/common/controller/blade-state-test.ts
Expand Up @@ -140,4 +140,26 @@ describe(exportBladeState.name, () => {
foo: 123,
});
});

it('should merge nested states', () => {
const state = exportBladeState(
() => ({
foo: {
bar: 1,
},
}),
{
foo: {
baz: 2,
},
},
);

assert.deepStrictEqual(state, {
foo: {
bar: 1,
baz: 2,
},
});
});
});
6 changes: 2 additions & 4 deletions packages/core/src/blade/common/controller/blade-state.ts
Expand Up @@ -3,6 +3,7 @@ import {
MicroParsers,
parseRecord,
} from '../../../common/micro-parsers';
import {deepMerge} from '../../../misc/type-util';

/**
* A state object for blades.
Expand Down Expand Up @@ -42,10 +43,7 @@ export function exportBladeState(
superExport: (() => BladeState) | null,
thisState: BladeState,
): BladeState {
return {
...superExport?.(),
...thisState,
};
return deepMerge(superExport?.() ?? {}, thisState);
}

/**
Expand Down
62 changes: 58 additions & 4 deletions packages/core/src/misc/type-util-test.ts
@@ -1,9 +1,9 @@
import * as assert from 'assert';
import {describe as context, describe, it} from 'mocha';
import {describe, it} from 'mocha';

import {deepEqualsArray, isPropertyWritable} from './type-util';
import {deepEqualsArray, deepMerge, isPropertyWritable} from './type-util';

describe('TypeUtil', () => {
describe(deepEqualsArray.name, () => {
[
{
expected: true,
Expand Down Expand Up @@ -34,13 +34,15 @@ describe('TypeUtil', () => {
},
},
].forEach(({expected, params}) => {
context(`when ${JSON.stringify(params)}`, () => {
describe(`when ${JSON.stringify(params)}`, () => {
it('should compare array deeply', () => {
assert.strictEqual(deepEqualsArray(params.a1, params.a2), expected);
});
});
});
});

describe(isPropertyWritable.name, () => {
it('should detect setter of plain object', () => {
const obj = {foo: 0};
assert.strictEqual(isPropertyWritable(obj, 'foo'), true);
Expand Down Expand Up @@ -91,3 +93,55 @@ describe('TypeUtil', () => {
assert.strictEqual(isPropertyWritable(wos, 'foo'), false);
});
});

describe(deepMerge.name, () => {
[
{
params: {
obj1: {
foo: {bar: 1},
},
obj2: {
foo: {baz: 2},
},
},
expected: {
foo: {bar: 1, baz: 2},
},
},
{
params: {
obj1: {foo: 1},
obj2: {bar: 2},
},
expected: {foo: 1, bar: 2},
},
{
params: {
obj1: {foo: 1},
obj2: {foo: 2, bar: 3},
},
expected: {foo: 2, bar: 3},
},
{
params: {
obj1: {foo: 1},
obj2: {foo: null},
},
expected: {foo: null},
},
{
params: {
obj1: {foo: 1},
obj2: {foo: undefined},
},
expected: {foo: undefined},
},
].forEach(({params, expected}) => {
describe(`when params=${JSON.stringify(params)}`, () => {
it('should merge object deeply', () => {
assert.deepStrictEqual(deepMerge(params.obj1, params.obj2), expected);
});
});
});
});
22 changes: 22 additions & 0 deletions packages/core/src/misc/type-util.ts
Expand Up @@ -47,3 +47,25 @@ export function isPropertyWritable(obj: unknown, key: string): boolean {

return false;
}

export function deepMerge(
r1: Record<string, unknown>,
r2: Record<string, unknown>,
): Record<string, unknown> {
const keys = Array.from(
new Set<string>([...Object.keys(r1), ...Object.keys(r2)]),
);
return keys.reduce((result, key) => {
const v1 = r1[key];
const v2 = r2[key];
return isRecord(v1) && isRecord(v2)
? {
...result,
[key]: deepMerge(v1, v2),
}
: {
...result,
[key]: key in r2 ? v2 : v1,
};
}, {});
}
7 changes: 4 additions & 3 deletions packages/tweakpane/src/doc/template/migration/v4/index.html
Expand Up @@ -140,12 +140,13 @@ <h2 id="preset"><a href="#preset">Importing/Exporting a preset</a></h2>
<div class="demo_code">
<div class="codeBlock"><pre><code class="js">function <strong>stateToPreset</strong>(state) {
if ('children' in state) {
return state.children.reduce((tmp, s) => {
return state.children.reduce((tmp, s) =&gt; {
return {...tmp, ...stateToPreset(s)};
}, {});
}
if ('key' in state && 'value' in state) {
return {[state.key]: state.value};
const binding = state.binding ?? {};
if ('key' in binding && 'value' in binding) {
return {[binding.key]: binding.value};
}
return {};
}</code></pre></div>
Expand Down

0 comments on commit 058998c

Please sign in to comment.