Spaces:
Runtime error
Runtime error
| <script lang="ts"> | |
| import {afterUpdate } from "svelte"; | |
| import Dmol from "3dmol"; | |
| import {BlockLabel, Empty } from "@gradio/atoms"; | |
| import {Image} from "@gradio/icons"; | |
| export let show_label = true; | |
| export let label: string; | |
| export let value: { molecule: string; caption: string | null }[] | null = null; | |
| export let columns: number | number[] | undefined = [2]; | |
| export let rows: number | number[] | undefined = undefined; | |
| export let height: number | "auto" = "auto"; | |
| export let automatic_rotation: boolean = true; | |
| // tracks whether the value of the gallery was reset | |
| let was_reset = true; | |
| $: was_reset = value == null || value.length == 0 ? true : was_reset; | |
| let _value: { molecule: string; caption: string | null }[] | null = null; | |
| $: _value = | |
| value === null | |
| ? null | |
| : value.map((data, i) => ({ | |
| molecule: data.molecule, | |
| caption: data.caption, | |
| })); | |
| let client_height = 0; | |
| let window_height = 0; | |
| // Function to initialize 3Dmol.js for each molecule | |
| function initializeMoleculeViewer(molecule: string, containerId: string): Dmol.GLViewer { | |
| let viewer = Dmol.createViewer(containerId, {}); | |
| viewer.addModel(molecule, "pdb" + {_value}); | |
| viewer.setStyle({ stick: {} }); | |
| viewer.setBackgroundColor("white"); | |
| viewer.zoomTo(); | |
| viewer.render(); | |
| return viewer; | |
| } | |
| // Rotate the viewer automatically | |
| function rotateMolecule(viewer: Dmol.GLViewer) { | |
| viewer.rotate(0.3, 'y') | |
| viewer.rotate(0.3, 'x') | |
| requestAnimationFrame((time) => rotateMolecule(viewer)); | |
| } | |
| // function to download the image on right click | |
| function handleContextMenu(event) { | |
| event.preventDefault(); | |
| // Get the data URL of the canvas | |
| const canvas = event.target; | |
| const canvas_id = event.currentTarget.id; | |
| console.log(canvas_id); | |
| var dt = canvas.toDataURL('image/png'); | |
| // Trigger the download | |
| var link = document.createElement('a'); | |
| link.href = dt; | |
| link.download = canvas_id + '.png'; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| afterUpdate(() => { | |
| // Trigger initialization when the component is mounted | |
| if (_value) { | |
| _value.forEach((entry, i) => { | |
| const containerId = 'mol-canvas-id-' + (i + 1); | |
| let viewer = initializeMoleculeViewer(entry.molecule, containerId); | |
| if (automatic_rotation) { | |
| rotateMolecule(viewer); | |
| } | |
| }); | |
| } | |
| }); | |
| </script> | |
| <svelte:window bind:innerHeight={window_height} /> | |
| {#if show_label} | |
| <BlockLabel {show_label} Icon={Image} label={label || "Gallery"} /> | |
| {/if} | |
| {#if value === null || _value === null || _value.length === 0} | |
| <Empty unpadded_box={true} size="large"><Image /></Empty> | |
| {:else} | |
| <div | |
| bind:clientHeight={client_height} | |
| class="grid-wrap" | |
| class:fixed-height={!height || height == "auto"} | |
| > | |
| <div | |
| class="grid-container" | |
| style="--grid-cols:{columns}; --grid-rows:{rows}; height: {height};" | |
| class:pt-6={show_label} | |
| > | |
| {#each _value as entry, i} | |
| <div | |
| class="molecule-item molecule-lg, mol-container" | |
| aria-label={"Molecule " + (i + 1) + " of " + _value.length} | |
| > | |
| <!-- svelte-ignore a11y-no-static-element-interactions --> | |
| <div on:contextmenu={handleContextMenu} id={'mol-canvas-id-' + (i + 1)} class="mol-canvas"></div> | |
| {#if entry.caption} | |
| <div class="caption-label"> | |
| {entry.caption} | |
| </div> | |
| {/if} | |
| </div> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| <style lang="postcss"> | |
| .fixed-height { | |
| min-height: var(--size-80); | |
| max-height: 55vh; | |
| } | |
| (--screen-xl) { | |
| .fixed-height { | |
| min-height: 450px; | |
| } | |
| } | |
| .molecule-item { | |
| --ring-color: transparent; | |
| position: relative; | |
| box-shadow: | |
| 0 0 0 2px var(--ring-color), | |
| var(--shadow-drop); | |
| border: 1px solid var(--border-color-primary); | |
| border-radius: var(--button-small-radius); | |
| background: var(--background-fill-secondary); | |
| aspect-ratio: var(--ratio-square); | |
| width: var(--size-full); | |
| height: var(--size-full); | |
| overflow: clip; | |
| } | |
| .molecule-item:hover { | |
| --ring-color: var(--color-accent); | |
| filter: brightness(1.1); | |
| } | |
| .grid-wrap { | |
| position: relative; | |
| padding: var(--size-2); | |
| height: var(--size-full); | |
| overflow-y: scroll; | |
| } | |
| .grid-container { | |
| display: grid; | |
| position: relative; | |
| grid-template-rows: repeat(var(--grid-rows), minmax(100px, 1fr)); | |
| grid-template-columns: repeat(var(--grid-cols), minmax(100px, 1fr)); | |
| grid-auto-rows: minmax(100px, 1fr); | |
| gap: var(--spacing-lg); | |
| } | |
| .mol-canvas { | |
| width: 100%; | |
| height: 100%; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| } | |
| .caption-label { | |
| position: absolute; | |
| right: var(--block-label-margin); | |
| bottom: var(--block-label-margin); | |
| z-index: var(--layer-1); | |
| border-top: 1px solid var(--border-color-primary); | |
| border-left: 1px solid var(--border-color-primary); | |
| border-radius: var(--block-label-radius); | |
| background: var(--background-fill-secondary); | |
| padding: var(--block-label-padding); | |
| max-width: 80%; | |
| overflow: hidden; | |
| font-size: var(--block-label-text-size); | |
| text-align: left; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| </style> | |