component.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. /**
  2. * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { VisualQualityOptions } from '../../../mol-geo/geometry/base';
  8. import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
  9. import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
  10. import { structureAreEqual, structureAreIntersecting, structureIntersect, structureSubtract, structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
  11. import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
  12. import { PluginContext } from '../../../mol-plugin/context';
  13. import { StateBuilder, StateObjectRef, StateTransformer } from '../../../mol-state';
  14. import { Task } from '../../../mol-task';
  15. import { ColorTheme } from '../../../mol-theme/color';
  16. import { SizeTheme } from '../../../mol-theme/size';
  17. import { UUID } from '../../../mol-util';
  18. import { ColorNames } from '../../../mol-util/color/names';
  19. import { objectForEach } from '../../../mol-util/object';
  20. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  21. import { StructureRepresentationPresetProvider } from '../../builder/structure/representation-preset';
  22. import { StatefulPluginComponent } from '../../component';
  23. import { StructureComponentParams } from '../../helpers/structure-component';
  24. import { setStructureOverpaint } from '../../helpers/structure-overpaint';
  25. import { createStructureColorThemeParams, createStructureSizeThemeParams } from '../../helpers/structure-representation-params';
  26. import { StructureSelectionQueries, StructureSelectionQuery } from '../../helpers/structure-selection-query';
  27. import { StructureRepresentation3D } from '../../transforms/representation';
  28. import { StructureHierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
  29. import { Clipping } from '../../../mol-theme/clipping';
  30. import { setStructureClipping } from '../../helpers/structure-clipping';
  31. import { setStructureTransparency } from '../../helpers/structure-transparency';
  32. import { StructureFocusRepresentation } from '../../../mol-plugin/behavior/dynamic/selection/structure-focus-representation';
  33. import { setStructureSubstance } from '../../helpers/structure-substance';
  34. import { Material } from '../../../mol-util/material';
  35. import { Clip } from '../../../mol-util/clip';
  36. export { StructureComponentManager };
  37. interface StructureComponentManagerState {
  38. options: StructureComponentManager.Options
  39. }
  40. class StructureComponentManager extends StatefulPluginComponent<StructureComponentManagerState> {
  41. readonly events = {
  42. optionsUpdated: this.ev<undefined>()
  43. }
  44. get currentStructures() {
  45. return this.plugin.managers.structure.hierarchy.selection.structures;
  46. }
  47. get pivotStructure(): StructureRef | undefined {
  48. return this.currentStructures[0];
  49. }
  50. async setOptions(options: StructureComponentManager.Options) {
  51. const interactionChanged = options.interactions !== this.state.options.interactions;
  52. this.updateState({ options });
  53. this.events.optionsUpdated.next(void 0);
  54. const update = this.dataState.build();
  55. for (const s of this.currentStructures) {
  56. for (const c of s.components) {
  57. this.updateReprParams(update, c);
  58. }
  59. }
  60. return this.plugin.dataTransaction(async () => {
  61. await update.commit();
  62. await this.plugin.state.updateBehavior(StructureFocusRepresentation, p => {
  63. p.ignoreHydrogens = !options.showHydrogens;
  64. p.material = options.materialStyle;
  65. p.clip = options.clipObjects;
  66. });
  67. if (interactionChanged) await this.updateInterationProps();
  68. });
  69. }
  70. private updateReprParams(update: StateBuilder.Root, component: StructureComponentRef) {
  71. const { showHydrogens, visualQuality: quality, materialStyle: material, clipObjects: clip } = this.state.options;
  72. const ignoreHydrogens = !showHydrogens;
  73. for (const r of component.representations) {
  74. if (r.cell.transform.transformer !== StructureRepresentation3D) continue;
  75. const params = r.cell.transform.params as StateTransformer.Params<StructureRepresentation3D>;
  76. if (!!params.type.params.ignoreHydrogens !== ignoreHydrogens || params.type.params.quality !== quality || params.type.params.material !== material || !PD.areEqual(Clip.Params, params.type.params.clip, clip)) {
  77. update.to(r.cell).update(old => {
  78. old.type.params.ignoreHydrogens = ignoreHydrogens;
  79. old.type.params.quality = quality;
  80. old.type.params.material = material;
  81. old.type.params.clip = clip;
  82. });
  83. }
  84. }
  85. }
  86. private async updateInterationProps() {
  87. for (const s of this.currentStructures) {
  88. const interactionParams = InteractionsProvider.getParams(s.cell.obj?.data!);
  89. if (s.properties) {
  90. const oldParams = s.properties.cell.transform.params?.properties[InteractionsProvider.descriptor.name];
  91. if (PD.areEqual(interactionParams, oldParams, this.state.options.interactions)) continue;
  92. await this.dataState.build().to(s.properties.cell)
  93. .update(old => {
  94. old.properties[InteractionsProvider.descriptor.name] = this.state.options.interactions;
  95. })
  96. .commit();
  97. } else {
  98. const pd = this.plugin.customStructureProperties.getParams(s.cell.obj?.data);
  99. const params = PD.getDefaultValues(pd);
  100. if (PD.areEqual(interactionParams, params.properties[InteractionsProvider.descriptor.name], this.state.options.interactions)) continue;
  101. params.properties[InteractionsProvider.descriptor.name] = this.state.options.interactions;
  102. await this.plugin.builders.structure.insertStructureProperties(s.cell, params);
  103. }
  104. }
  105. }
  106. applyPreset<P extends StructureRepresentationPresetProvider>(structures: ReadonlyArray<StructureRef>, provider: P, params?: StructureRepresentationPresetProvider.Params<P>): Promise<any> {
  107. return this.plugin.dataTransaction(async () => {
  108. for (const s of structures) {
  109. const preset = await this.plugin.builders.structure.representation.applyPreset(s.cell, provider, params);
  110. await this.syncPreset(s, preset);
  111. }
  112. }, { canUndo: 'Preset' });
  113. }
  114. private syncPreset(root: StructureRef, preset?: StructureRepresentationPresetProvider.Result) {
  115. if (!preset || !preset.components) return this.clearComponents([root]);
  116. const keptRefs = new Set<string>();
  117. objectForEach(preset.components, c => {
  118. if (c) keptRefs.add(c.ref);
  119. });
  120. if (preset.representations) {
  121. objectForEach(preset.representations, r => {
  122. if (r) keptRefs.add(r.ref);
  123. });
  124. }
  125. if (keptRefs.size === 0) return this.clearComponents([root]);
  126. let changed = false;
  127. const update = this.dataState.build();
  128. const sync = (r: StructureHierarchyRef) => {
  129. if (!keptRefs.has(r.cell.transform.ref)) {
  130. changed = true;
  131. update.delete(r.cell);
  132. }
  133. };
  134. for (const c of root.components) {
  135. sync(c);
  136. for (const r of c.representations) sync(r);
  137. if (c.genericRepresentations) {
  138. for (const r of c.genericRepresentations) sync(r);
  139. }
  140. }
  141. if (root.genericRepresentations) {
  142. for (const r of root.genericRepresentations) {
  143. sync(r);
  144. }
  145. }
  146. if (changed) return update.commit();
  147. }
  148. clear(structures: ReadonlyArray<StructureRef>) {
  149. return this.clearComponents(structures);
  150. }
  151. selectThis(components: ReadonlyArray<StructureComponentRef>) {
  152. const mng = this.plugin.managers.structure.selection;
  153. mng.clear();
  154. for (const c of components) {
  155. const loci = Structure.toSubStructureElementLoci(c.structure.cell.obj!.data, c.cell.obj?.data!);
  156. mng.fromLoci('set', loci);
  157. }
  158. }
  159. canBeModified(ref: StructureHierarchyRef) {
  160. return this.plugin.builders.structure.isComponentTransform(ref.cell);
  161. }
  162. modifyByCurrentSelection(components: ReadonlyArray<StructureComponentRef>, action: StructureComponentManager.ModifyAction) {
  163. return this.plugin.runTask(Task.create('Modify Component', async taskCtx => {
  164. const b = this.dataState.build();
  165. for (const c of components) {
  166. if (!this.canBeModified(c)) continue;
  167. const selection = this.plugin.managers.structure.selection.getStructure(c.structure.cell.obj!.data);
  168. if (!selection || selection.elementCount === 0) continue;
  169. this.modifyComponent(b, c, selection, action);
  170. }
  171. await this.dataState.updateTree(b, { canUndo: 'Modify Selection' }).runInContext(taskCtx);
  172. }));
  173. }
  174. toggleVisibility(components: ReadonlyArray<StructureComponentRef>, reprPivot?: StructureRepresentationRef) {
  175. if (components.length === 0) return;
  176. if (!reprPivot) {
  177. const isHidden = !components[0].cell.state.isHidden;
  178. for (const c of components) {
  179. setSubtreeVisibility(this.dataState, c.cell.transform.ref, isHidden);
  180. }
  181. } else {
  182. const index = components[0].representations.indexOf(reprPivot);
  183. const isHidden = !reprPivot.cell.state.isHidden;
  184. for (const c of components) {
  185. // TODO: is it ok to use just the index here? Could possible lead to ugly edge cases, but perhaps not worth the trouble to "fix".
  186. const repr = c.representations[index];
  187. if (!repr) continue;
  188. setSubtreeVisibility(this.dataState, repr.cell.transform.ref, isHidden);
  189. }
  190. }
  191. }
  192. removeRepresentations(components: ReadonlyArray<StructureComponentRef>, pivot?: StructureRepresentationRef) {
  193. if (components.length === 0) return;
  194. const toRemove: StructureHierarchyRef[] = [];
  195. if (pivot) {
  196. const index = components[0].representations.indexOf(pivot);
  197. if (index < 0) return;
  198. for (const c of components) {
  199. if (c.representations[index]) toRemove.push(c.representations[index]);
  200. }
  201. } else {
  202. for (const c of components) {
  203. for (const r of c.representations) {
  204. toRemove.push(r);
  205. }
  206. }
  207. }
  208. return this.plugin.managers.structure.hierarchy.remove(toRemove, true);
  209. }
  210. updateRepresentations(components: ReadonlyArray<StructureComponentRef>, pivot: StructureRepresentationRef, params: StateTransformer.Params<StructureRepresentation3D>) {
  211. if (components.length === 0) return Promise.resolve();
  212. const index = components[0].representations.indexOf(pivot);
  213. if (index < 0) return Promise.resolve();
  214. const update = this.dataState.build();
  215. for (const c of components) {
  216. // TODO: is it ok to use just the index here? Could possible lead to ugly edge cases, but perhaps not worth the trouble to "fix".
  217. const repr = c.representations[index];
  218. if (!repr) continue;
  219. if (repr.cell.transform.transformer !== pivot.cell.transform.transformer) continue;
  220. update.to(repr.cell).update(params);
  221. }
  222. return update.commit({ canUndo: 'Update Representation' });
  223. }
  224. /**
  225. * To update theme for all selected structures, use
  226. * plugin.dataTransaction(async () => {
  227. * for (const s of structure.hierarchy.selection.structures) await updateRepresentationsTheme(s.componets, ...);
  228. * }, { canUndo: 'Update Theme' });
  229. */
  230. updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> | undefined
  231. updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: (c: StructureComponentRef, r: StructureRepresentationRef) => StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> | undefined
  232. updateRepresentationsTheme(components: ReadonlyArray<StructureComponentRef>, paramsOrProvider: StructureComponentManager.UpdateThemeParams<any, any> | ((c: StructureComponentRef, r: StructureRepresentationRef) => StructureComponentManager.UpdateThemeParams<any, any>)) {
  233. if (components.length === 0) return;
  234. const update = this.dataState.build();
  235. for (const c of components) {
  236. for (const repr of c.representations) {
  237. const old = repr.cell.transform.params;
  238. const params: StructureComponentManager.UpdateThemeParams<any, any> = typeof paramsOrProvider === 'function' ? paramsOrProvider(c, repr) : paramsOrProvider;
  239. const colorTheme = params.color === 'default'
  240. ? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name)
  241. : params.color
  242. ? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.color, params.colorParams)
  243. : void 0;
  244. const sizeTheme = params.size === 'default'
  245. ? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name)
  246. : params.color
  247. ? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.size, params.sizeParams)
  248. : void 0;
  249. if (colorTheme || sizeTheme) {
  250. update.to(repr.cell).update(prev => {
  251. if (colorTheme) prev.colorTheme = colorTheme;
  252. if (sizeTheme) prev.sizeTheme = sizeTheme;
  253. });
  254. }
  255. }
  256. }
  257. return update.commit({ canUndo: 'Update Theme' });
  258. }
  259. addRepresentation(components: ReadonlyArray<StructureComponentRef>, type: string) {
  260. if (components.length === 0) return;
  261. const { showHydrogens, visualQuality: quality, materialStyle: material, clipObjects: clip } = this.state.options;
  262. const ignoreHydrogens = !showHydrogens;
  263. const typeParams = { ignoreHydrogens, quality, material, clip };
  264. return this.plugin.dataTransaction(async () => {
  265. for (const component of components) {
  266. await this.plugin.builders.structure.representation.addRepresentation(component.cell, {
  267. type: this.plugin.representation.structure.registry.get(type),
  268. typeParams
  269. });
  270. }
  271. }, { canUndo: 'Add Representation' });
  272. }
  273. private tryFindComponent(structure: StructureRef, selection: StructureSelectionQuery) {
  274. if (structure.components.length === 0) return;
  275. return this.plugin.runTask(Task.create('Find Component', async taskCtx => {
  276. const data = structure.cell.obj?.data;
  277. if (!data) return;
  278. const sel = StructureSelection.unionStructure(await selection.getSelection(this.plugin, taskCtx, data));
  279. for (const c of structure.components) {
  280. const comp = c.cell.obj?.data;
  281. if (!comp || !c.cell.parent) continue;
  282. if (structureAreEqual(sel, comp)) return c.cell;
  283. }
  284. }));
  285. }
  286. async add(params: StructureComponentManager.AddParams, structures?: ReadonlyArray<StructureRef>) {
  287. return this.plugin.dataTransaction(async () => {
  288. const xs = structures || this.currentStructures;
  289. if (xs.length === 0) return;
  290. const { showHydrogens, visualQuality: quality, materialStyle: material, clipObjects: clip } = this.state.options;
  291. const ignoreHydrogens = !showHydrogens;
  292. const typeParams = { ignoreHydrogens, quality, material, clip };
  293. const componentKey = UUID.create22();
  294. for (const s of xs) {
  295. let component: StateObjectRef | undefined = void 0;
  296. if (params.options.checkExisting) {
  297. component = await this.tryFindComponent(s, params.selection);
  298. }
  299. if (!component) {
  300. component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.cell, params.selection, componentKey, {
  301. label: params.options.label || (params.selection === StructureSelectionQueries.current ? 'Custom Selection' : ''),
  302. });
  303. }
  304. if (params.representation === 'none' || !component) continue;
  305. await this.plugin.builders.structure.representation.addRepresentation(component, {
  306. type: this.plugin.representation.structure.registry.get(params.representation),
  307. typeParams
  308. });
  309. }
  310. }, { canUndo: 'Add Selection' });
  311. }
  312. async applyTheme(params: StructureComponentManager.ThemeParams, structures?: ReadonlyArray<StructureRef>) {
  313. return this.plugin.dataTransaction(async ctx => {
  314. const xs = structures || this.currentStructures;
  315. if (xs.length === 0) return;
  316. const getLoci = async (s: Structure) => StructureSelection.toLociWithSourceUnits(await params.selection.getSelection(this.plugin, ctx, s));
  317. for (const s of xs) {
  318. if (params.action.name === 'color') {
  319. const p = params.action.params;
  320. await setStructureOverpaint(this.plugin, s.components, p.color, getLoci, params.representations);
  321. } else if (params.action.name === 'resetColor') {
  322. await setStructureOverpaint(this.plugin, s.components, -1, getLoci, params.representations);
  323. } else if (params.action.name === 'transparency') {
  324. const p = params.action.params;
  325. await setStructureTransparency(this.plugin, s.components, p.value, getLoci, params.representations);
  326. } else if (params.action.name === 'material') {
  327. const p = params.action.params;
  328. await setStructureSubstance(this.plugin, s.components, p.material, getLoci, params.representations);
  329. } else if (params.action.name === 'resetMaterial') {
  330. await setStructureSubstance(this.plugin, s.components, void 0, getLoci, params.representations);
  331. } else if (params.action.name === 'clipping') {
  332. const p = params.action.params;
  333. await setStructureClipping(this.plugin, s.components, Clipping.Groups.fromNames(p.excludeGroups), getLoci, params.representations);
  334. }
  335. }
  336. }, { canUndo: 'Apply Theme' });
  337. }
  338. private modifyComponent(builder: StateBuilder.Root, component: StructureComponentRef, by: Structure, action: StructureComponentManager.ModifyAction) {
  339. const structure = component.cell.obj?.data;
  340. if (!structure) return;
  341. if ((action === 'subtract' || action === 'intersect') && !structureAreIntersecting(structure, by)) return;
  342. const parent = component.structure.cell.obj?.data!;
  343. const modified = action === 'union'
  344. ? structureUnion(parent, [structure, by])
  345. : action === 'intersect'
  346. ? structureIntersect(structure, by)
  347. : structureSubtract(structure, by);
  348. if (modified.elementCount === 0) {
  349. builder.delete(component.cell.transform.ref);
  350. } else {
  351. const bundle = StructureElement.Bundle.fromSubStructure(parent, modified);
  352. const params: StructureComponentParams = {
  353. type: { name: 'bundle', params: bundle },
  354. nullIfEmpty: true,
  355. label: component.cell.obj?.label!
  356. };
  357. builder.to(component.cell).update(params);
  358. }
  359. }
  360. updateLabel(component: StructureComponentRef, label: string) {
  361. const params: StructureComponentParams = {
  362. type: component.cell.params?.values.type,
  363. nullIfEmpty: component.cell.params?.values.nullIfEmpty,
  364. label
  365. };
  366. this.dataState.build().to(component.cell).update(params).commit();
  367. }
  368. private get dataState() {
  369. return this.plugin.state.data;
  370. }
  371. private clearComponents(structures: ReadonlyArray<StructureRef>) {
  372. const deletes = this.dataState.build();
  373. for (const s of structures) {
  374. for (const c of s.components) {
  375. deletes.delete(c.cell.transform.ref);
  376. }
  377. }
  378. return deletes.commit({ canUndo: 'Clear Selections' });
  379. }
  380. constructor(public plugin: PluginContext) {
  381. super({ options: PD.getDefaultValues(StructureComponentManager.OptionsParams) });
  382. }
  383. }
  384. namespace StructureComponentManager {
  385. export const OptionsParams = {
  386. showHydrogens: PD.Boolean(true, { description: 'Toggle display of hydrogen atoms in representations' }),
  387. visualQuality: PD.Select('auto', VisualQualityOptions, { description: 'Control the visual/rendering quality of representations' }),
  388. materialStyle: Material.getParam(),
  389. clipObjects: PD.Group(Clip.Params),
  390. interactions: PD.Group(InteractionsProvider.defaultParams, { label: 'Non-covalent Interactions' }),
  391. };
  392. export type Options = PD.Values<typeof OptionsParams>
  393. export function getAddParams(plugin: PluginContext, params?: { pivot?: StructureRef, allowNone: boolean, hideSelection?: boolean, checkExisting?: boolean }) {
  394. const { options } = plugin.query.structure.registry;
  395. params = {
  396. pivot: plugin.managers.structure.component.pivotStructure,
  397. allowNone: true,
  398. hideSelection: false,
  399. checkExisting: false,
  400. ...params
  401. };
  402. return {
  403. selection: PD.Select(options[1][0], options, { isHidden: params?.hideSelection }),
  404. representation: getRepresentationTypesSelect(plugin, params?.pivot, params?.allowNone ? [['none', '< Create Later >']] : []),
  405. options: PD.Group({
  406. label: PD.Text(''),
  407. checkExisting: PD.Boolean(!!params?.checkExisting, { help: () => ({ description: 'Checks if a selection with the specifield elements already exists to avoid creating duplicate components.' }) }),
  408. })
  409. };
  410. }
  411. export type AddParams = { selection: StructureSelectionQuery, options: { checkExisting: boolean, label: string }, representation: string }
  412. export function getThemeParams(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) {
  413. const { options } = plugin.query.structure.registry;
  414. return {
  415. selection: PD.Select(options[1][0], options, { isHidden: false }),
  416. action: PD.MappedStatic('color', {
  417. color: PD.Group({
  418. color: PD.Color(ColorNames.blue, { isExpanded: true }),
  419. }, { isFlat: true }),
  420. resetColor: PD.EmptyGroup({ label: 'Reset Color' }),
  421. transparency: PD.Group({
  422. value: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
  423. }, { isFlat: true }),
  424. material: PD.Group({
  425. material: Material.getParam({ isFlat: true }),
  426. }, { isFlat: true }),
  427. resetMaterial: PD.EmptyGroup({ label: 'Reset Material' }),
  428. clipping: PD.Group({
  429. excludeGroups: PD.MultiSelect([] as Clipping.Groups.Names[], PD.objectToOptions(Clipping.Groups.Names)),
  430. }, { isFlat: true }),
  431. }),
  432. representations: PD.MultiSelect([], getRepresentationTypes(plugin, pivot), { emptyValue: 'All' })
  433. };
  434. }
  435. export type ThemeParams = PD.Values<ReturnType<typeof getThemeParams>>
  436. export function getRepresentationTypes(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) {
  437. return pivot?.cell.obj?.data
  438. ? plugin.representation.structure.registry.getApplicableTypes(pivot.cell.obj?.data!)
  439. : plugin.representation.structure.registry.types;
  440. }
  441. function getRepresentationTypesSelect(plugin: PluginContext, pivot: StructureRef | undefined, custom: [string, string][], label?: string) {
  442. const types = [
  443. ...custom,
  444. ...getRepresentationTypes(plugin, pivot)
  445. ] as [string, string][];
  446. return PD.Select(types[0][0], types, { label });
  447. }
  448. export type ModifyAction = 'union' | 'subtract' | 'intersect'
  449. export interface UpdateThemeParams<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn> {
  450. /**
  451. * this works for any theme name (use 'name as any'), but code completion will break
  452. */
  453. color?: C | 'default',
  454. colorParams?: ColorTheme.BuiltInParams<C>,
  455. size?: S | 'default',
  456. sizeParams?: SizeTheme.BuiltInParams<S>
  457. }
  458. }