diff --git a/src/options.ts b/src/options.ts index 3850055ec..1f850bf87 100644 --- a/src/options.ts +++ b/src/options.ts @@ -12,7 +12,7 @@ import { Settings } from './dataset'; * Possible HTML attributes to attach to a setting */ // this is mostly to catch typo early. Feel free to add more! -type Attribute = 'value' | 'checked' | 'innerText'; +type Attribute = 'value' | 'checked' | 'innerText' | 'options'; /// Type mapping for options interface OptionsTypeMap { @@ -146,8 +146,17 @@ export class HTMLOption { */ public changed(origin: OptionModificationOrigin) { for (const bound of this._boundList) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - (bound.element as any)[bound.attribute] = this._value; + if (bound.attribute === 'options') { + // options take a list of comma-separated values to allow multiple settings + const values = (this._value as string).split(','); + const element = bound.element as HTMLSelectElement; + for (let option of element.options) { + option.selected = values.includes(option.value); + } + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + (bound.element as any)[bound.attribute] = this._value; + } } for (const callback of this.onchange) { @@ -175,14 +184,27 @@ export class HTMLOption { } element = element as HTMLElement; - const listener = (event: Event) => { - assert(event.target !== null); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument - this._update((event.target as any)[attribute].toString(), 'DOM'); - }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - (element as any)[attribute] = this._value; + let listener: (event: Event) => void; + if (attribute === 'options') { + listener = (event: Event) => { + // we need a special handler for multi-select options + assert(event.target !== null); + const element = event.target as HTMLSelectElement; + const values: string[] = Array.from(element.options) + .filter((option) => option.selected) + .map((option) => option.value); + + this._update(values.toString(), 'DOM'); + }; + } else { + listener = (event: Event) => { + assert(event.target !== null); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument + this._update((event.target as any)[attribute].toString(), 'DOM'); + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (element as any)[attribute] = this._value; + } element.addEventListener('change', listener); this._boundList.push({ element, attribute, listener }); diff --git a/src/structure/options.html.in b/src/structure/options.html.in index 4cd4ebac9..d1f977e47 100644 --- a/src/structure/options.html.in +++ b/src/structure/options.html.in @@ -35,8 +35,8 @@
- - + +
diff --git a/src/structure/viewer.ts b/src/structure/viewer.ts index 1603a84d5..946f83688 100644 --- a/src/structure/viewer.ts +++ b/src/structure/viewer.ts @@ -433,12 +433,11 @@ export class MoleculeViewer { const selectShape = this._options.getModalElement('shapes'); selectShape.options.length = 0; - selectShape.options.add(new Option('off', '')); for (const key of Object.keys(structure['shapes'])) { selectShape.options.add(new Option(key, key)); } - this._options.shape.bind(selectShape, 'value'); + this._options.shape.bind(selectShape, 'options'); } this._updateStyle(); @@ -916,52 +915,58 @@ export class MoleculeViewer { assert(this._current.atomLabels.length === 0); const structure = this._current.structure; - assert(!(structure.shapes === undefined)); - const current_shape = structure.shapes[this._options.shape.value]; - assert(!(current_shape === undefined)); - const supercell_a = this._options.supercell[0].value; - const supercell_b = this._options.supercell[1].value; - const supercell_c = this._options.supercell[2].value; - let cell = this._current.structure.cell; + const active_shapes = (this._options.shape.value as string).split(','); - if ((supercell_a > 1 || supercell_b > 1 || supercell_c > 1) && cell === undefined) { - return; - } else if (cell === undefined) { - cell = [1, 0, 0, 0, 1, 0, 0, 0, 1]; - } + for (let shape of active_shapes) { + if (shape === '') { + continue; + } + assert(shape in structure.shapes); + const current_shape = structure.shapes[shape]; + const supercell_a = this._options.supercell[0].value; + const supercell_b = this._options.supercell[1].value; + const supercell_c = this._options.supercell[2].value; + let cell = this._current.structure.cell; + + if ((supercell_a > 1 || supercell_b > 1 || supercell_c > 1) && cell === undefined) { + return; + } else if (cell === undefined) { + cell = [1, 0, 0, 0, 1, 0, 0, 0, 1]; + } - for (let a = 0; a < supercell_a; a++) { - for (let b = 0; b < supercell_b; b++) { - for (let c = 0; c < supercell_c; c++) { - for (let i = 0; i < structure.size; i++) { - const name = structure.names[i]; - const position: [number, number, number] = [ - structure.x[i] + a * cell[0] + b * cell[3] + c * cell[6], - structure.y[i] + a * cell[1] + b * cell[4] + c * cell[7], - structure.z[i] + a * cell[2] + b * cell[5] + c * cell[8], - ]; - - if (current_shape[i].kind === 'ellipsoid') { - const data = current_shape[i] as unknown as EllipsoidData; - const shape = new Ellipsoid(position, data); - this._viewer.addCustom( - shape.outputTo3Dmol($3Dmol.elementColors.Jmol[name] || 0x000000) - ); - } else if (current_shape[i].kind === 'custom') { - const data = current_shape[i] as unknown as CustomShapeData; - const shape = new CustomShape(position, data); - this._viewer.addCustom( - shape.outputTo3Dmol($3Dmol.elementColors.Jmol[name] || 0x000000) - ); - } else { - assert(current_shape[i].kind === 'sphere'); - const data = current_shape[i] as unknown as SphereData; - const shape = new Sphere(position, data); - this._viewer.addCustom( - shape.outputTo3Dmol($3Dmol.elementColors.Jmol[name] || 0x000000) - ); + for (let a = 0; a < supercell_a; a++) { + for (let b = 0; b < supercell_b; b++) { + for (let c = 0; c < supercell_c; c++) { + for (let i = 0; i < structure.size; i++) { + const name = structure.names[i]; + const position: [number, number, number] = [ + structure.x[i] + a * cell[0] + b * cell[3] + c * cell[6], + structure.y[i] + a * cell[1] + b * cell[4] + c * cell[7], + structure.z[i] + a * cell[2] + b * cell[5] + c * cell[8], + ]; + + if (current_shape[i].kind === 'ellipsoid') { + const data = current_shape[i] as unknown as EllipsoidData; + const shape = new Ellipsoid(position, data); + this._viewer.addCustom( + shape.outputTo3Dmol($3Dmol.elementColors.Jmol[name] || 0x000000) + ); + } else if (current_shape[i].kind === 'custom') { + const data = current_shape[i] as unknown as CustomShapeData; + const shape = new CustomShape(position, data); + this._viewer.addCustom( + shape.outputTo3Dmol($3Dmol.elementColors.Jmol[name] || 0x000000) + ); + } else { + assert(current_shape[i].kind === 'sphere'); + const data = current_shape[i] as unknown as SphereData; + const shape = new Sphere(position, data); + this._viewer.addCustom( + shape.outputTo3Dmol($3Dmol.elementColors.Jmol[name] || 0x000000) + ); + } } } }