diff --git a/apis/nucleus/src/object/__tests__/create-session-object.test.js b/apis/nucleus/src/object/__tests__/create-session-object.test.js index 965805935..943c5c956 100644 --- a/apis/nucleus/src/object/__tests__/create-session-object.test.js +++ b/apis/nucleus/src/object/__tests__/create-session-object.test.js @@ -53,8 +53,8 @@ describe('create-session-object', () => { const t = { initialProperties: jest.fn() }; t.initialProperties.mockReturnValue({ then: () => {} }); types.get.mockReturnValue(t); - create({ type: 't', version: 'v', fields: 'f', properties: 'props' }, halo); - expect(t.initialProperties).toHaveBeenCalledWith('props'); + create({ type: 't', version: 'v', fields: 'f', properties: 'props', extendProperties: false }, halo); + expect(t.initialProperties).toHaveBeenCalledWith('props', false); }); test('should populate fields', async () => { diff --git a/apis/nucleus/src/object/create-session-object.js b/apis/nucleus/src/object/create-session-object.js index fc6c6438a..1b6171239 100644 --- a/apis/nucleus/src/object/create-session-object.js +++ b/apis/nucleus/src/object/create-session-object.js @@ -13,17 +13,32 @@ import { subscribe, modelStore } from '../stores/model-store'; * @property {string} type * @property {string=} version * @property {(Field[])=} fields + * @property {boolean} [extendProperties=false] Whether to deeply extend properties or not. If false then subtrees will be overwritten. * @property {EngineAPI.IGenericObjectProperties=} properties + * @example + * // A config for Creating objects: + * const createConfig = { + * type: 'bar', + * element: document.querySelector('.bar'), + * extendProperties: true, + * fields: ['[Country names]', '=Sum(Sales)'], + * properties: { + * legend: { + * show: false, + * }, + * } + * }; + * nebbie.render(createConfig); */ export default async function createSessionObject( - { type, version, fields, properties, options, plugins, element }, + { type, version, fields, properties, options, plugins, element, extendProperties }, halo ) { let mergedProps = {}; let error; try { const t = halo.types.get({ name: type, version }); - mergedProps = await t.initialProperties(properties); + mergedProps = await t.initialProperties(properties, extendProperties); const sn = await t.supernova(); if (fields) { populateData( diff --git a/apis/nucleus/src/sn/__tests__/type.test.js b/apis/nucleus/src/sn/__tests__/type.test.js index a3f0502cf..30ab6f28f 100644 --- a/apis/nucleus/src/sn/__tests__/type.test.js +++ b/apis/nucleus/src/sn/__tests__/type.test.js @@ -90,5 +90,24 @@ describe('type', () => { showTitles: true, }); }); + + test('should allow deep extend', async () => { + const def = Promise.resolve('def'); + const normalized = { qae: { properties: { initial: { a: 'a', b: { c: 'c', d: 'd' } } } } }; + + load.mockResolvedValue(def); + SNFactory.mockReturnValue(normalized); + + const props = await c.initialProperties({ e: 'e', b: { c: 'override' } }, true); + expect(props).toEqual({ + qInfo: { qType: 'pie' }, + visualization: 'pie', + version: '1.1.0', + a: 'a', + b: { c: 'override', d: 'd' }, + e: 'e', + showTitles: true, + }); + }); }); }); diff --git a/apis/nucleus/src/sn/type.js b/apis/nucleus/src/sn/type.js index b84a60898..ccc06500e 100644 --- a/apis/nucleus/src/sn/type.js +++ b/apis/nucleus/src/sn/type.js @@ -1,5 +1,6 @@ import { generator as SNFactory } from '@nebula.js/supernova'; import { satisfies } from 'semver'; +import extend from 'extend'; import { load } from './load'; /** @@ -29,7 +30,7 @@ export default function create(info, halo, opts = {}) { stringified = JSON.stringify(sn.qae.properties.initial); return sn; }), - initialProperties(initial) { + initialProperties(initial, extendProperties = false) { return this.supernova().then(() => { const props = { qInfo: { @@ -39,9 +40,15 @@ export default function create(info, halo, opts = {}) { version: type.version, showTitles: true, ...JSON.parse(stringified), + }; + if (extendProperties) { + extend(true, props, initial); + return props; + } + return { + ...props, ...initial, }; - return props; }); }, }; diff --git a/apis/stardust/api-spec/spec.json b/apis/stardust/api-spec/spec.json index 0eed09883..7a5d3b573 100644 --- a/apis/stardust/api-spec/spec.json +++ b/apis/stardust/api-spec/spec.json @@ -1378,11 +1378,20 @@ } ] }, + "extendProperties": { + "description": "Whether to deeply extend properties or not. If false then subtrees will be overwritten.", + "optional": true, + "defaultValue": false, + "type": "boolean" + }, "properties": { "optional": true, "type": "EngineAPI.IGenericObjectProperties" } - } + }, + "examples": [ + "// A config for Creating objects:\nconst createConfig = {\n type: 'bar',\n element: document.querySelector('.bar'),\n extendProperties: true,\n fields: ['[Country names]', '=Sum(Sales)'],\n properties: {\n legend: {\n show: false,\n },\n }\n};\nnebbie.render(createConfig);" + ] }, "BaseConfig": { "description": "Basic rendering configuration for rendering an object", diff --git a/apis/stardust/types/index.d.ts b/apis/stardust/types/index.d.ts index 9a4744ee1..c454528e3 100644 --- a/apis/stardust/types/index.d.ts +++ b/apis/stardust/types/index.d.ts @@ -428,6 +428,7 @@ declare namespace stardust { type: string; version?: string; fields?: stardust.Field[]; + extendProperties?: boolean; properties?: EngineAPI.IGenericObjectProperties; } diff --git a/commands/serve/web/components/Visualize/Visualize.jsx b/commands/serve/web/components/Visualize/Visualize.jsx index d9d32d39b..3520ada65 100644 --- a/commands/serve/web/components/Visualize/Visualize.jsx +++ b/commands/serve/web/components/Visualize/Visualize.jsx @@ -10,6 +10,8 @@ import { Toolbar, Button, IconButton, + ToggleButtonGroup, + ToggleButton, Typography, Tab, Tabs, @@ -50,6 +52,16 @@ const languages = [ 'ru-RU', ]; +const constraints = []; + +function getContstraints(arr) { + return { + select: arr.indexOf('select') !== -1, + active: arr.indexOf('active') !== -1, + passive: arr.indexOf('passive') !== -1, + }; +} + export default function Visualize() { const uid = useRef(); const navigate = useNavigate(); @@ -62,6 +74,7 @@ export default function Visualize() { const [currentThemeName, setCurrentThemeName] = useState(storage.get('themeName')); const [currentLanguage, setCurrentLanguage] = useState(storage.get('language') || 'en-US'); const [currentMuiThemeName, setCurrentMuiThemeName] = useState('light'); + const [currentConstraints, setCurrentConstraints] = useState(constraints); const [objectListMode, setObjectListMode] = useState(storage.get('objectListMode') === true); const currentSelectionsRef = useRef(null); const [currentId, setCurrentId] = useState(); @@ -93,6 +106,7 @@ export default function Visualize() { context: { theme: currentThemeName, language: currentLanguage, + constraints: getContstraints(currentConstraints), keyboardNavigation: info?.keyboardNavigation, }, load: (type) => Promise.resolve(window[type.name]), @@ -180,6 +194,11 @@ export default function Visualize() { nebbie.context({ language: lang }); }; + const handleConstraintsChange = (e, newValue) => { + setCurrentConstraints(newValue); + nebbie.context(getContstraints(newValue)); + }; + const toggleDarkMode = () => { const v = currentThemeName === 'dark' ? 'light' : 'dark'; storage.save('themeName', v); @@ -238,6 +257,16 @@ export default function Visualize() { + + Constraints + + + + Select + Active + Passive + + {customThemes.length ? ( <>