1- import React , { useEffect , useState } from "react" ;
2- import { elements as allElements } from "./elements " ;
1+ import React , { useEffect , useState , useMemo } from "react" ;
2+ import { symbols , names } from "mc-periodic-table " ;
33
44import HelpIcon from "../../../common/HelpIcon" ;
5-
6- const defaultColors = {
7- group1 : "bg-[#d7bbbb]" ,
8- group2 : "bg-[#d7cdbb]" ,
9- group17 : "bg-[#d7d9c2]" ,
10- group18 : "bg-[#d0d5ee]" ,
11- transition : "bg-[#c4d0d8]" ,
12- lanthanide : "bg-[#e5cbee]" ,
13- actinide : "bg-[#aebcee]" ,
14- metalloid : "bg-[#cdd9c2]" ,
15- postTransition : "bg-[#c2c8d9]" ,
16- mainGroup : "bg-[#d9c2ce]" ,
17- default : "bg-gray-200" ,
18- } ;
5+ import { PTableWrapper } from "../../../common/PTableWrapper" ;
196
207export default function PTable ( {
218 providerUrl = null ,
229 selected = { } ,
2310 onSelectionChange,
24- colors = defaultColors ,
25- selectedClassName = "bg-green-400 border border-green-700 text-white scale-105" ,
26- deselectedClassName = "bg-red-400 border border-red-700 text-white scale-105" ,
27- defaultBorderClassName = "border border-slate-700" ,
28- hoverClassName = "transform transition-transform duration-100 hover:scale-105 hover:z-10 hover:cursor-pointer" ,
2911} ) {
30- const [ elements , setElements ] = useState ( allElements ) ;
3112 const [ cachedPTable , setCachedPTable ] = useState ( null ) ;
3213 const [ lastModified , setLastModified ] = useState ( null ) ;
3314
34- // Load cached PTable once on mount
15+ // ----------------------------
16+ // cache loading
17+ // ----------------------------
3518 useEffect ( ( ) => {
3619 const loadCache = async ( ) => {
3720 const urls = [
3821 "./cachedPTable.json" ,
3922 "https://raw.githubusercontent.com/materialscloud-org/tools-optimadeclient-react/main/public/cachedPTable.json" ,
4023 ] ;
24+
4125 let json = null ;
26+
4227 for ( const url of urls ) {
4328 try {
4429 const res = await fetch ( url ) ;
45- if ( ! res . ok )
46- throw new Error ( `Failed to load cached PTable from ${ url } ` ) ;
30+ if ( ! res . ok ) throw new Error ( `Failed ${ url } ` ) ;
4731 json = await res . json ( ) ;
48- console . log ( `Loaded cached PTable from ${ url } ` ) ;
4932 break ;
50- } catch ( err ) {
51- console . warn ( err . message ) ;
33+ } catch ( e ) {
34+ console . warn ( e . message ) ;
5235 }
5336 }
5437
55- if ( ! json ) {
56- console . error ( "Failed to load cached PTable from all sources" ) ;
57- return ;
58- }
38+ if ( ! json ) return ;
5939
60- // Use lastUpdated for UI
6140 if ( json . lastUpdated ) {
6241 setLastModified ( new Date ( json . lastUpdated ) . toLocaleDateString ( ) ) ;
6342 }
6443
65- // Convert to lookup map
6644 const map = { } ;
6745 json . data . forEach ( ( entry ) => {
6846 if ( ! entry . providerUrl ) return ;
6947
7048 const key = entry . providerUrl . replace ( / \/ + $ / , "" ) ;
71- // Check if `ptable` exists, otherwise check flattened object
49+
7250 const ptable =
7351 entry . ptable ??
7452 Object . fromEntries (
@@ -79,93 +57,81 @@ export default function PTable({
7957
8058 map [ key ] = ptable ;
8159 } ) ;
60+
8261 setCachedPTable ( map ) ;
8362 } ;
8463
8564 loadCache ( ) ;
8665 } , [ ] ) ;
8766
88- // Update elements when providerUrl changes
89- useEffect ( ( ) => {
90- if ( ! providerUrl || ! cachedPTable ) return ;
67+ // ----------------------------
68+ // convert selected symbols → atomic state
69+ // ----------------------------
70+ const selectionState = useMemo ( ( ) => {
71+ const atomic = { } ;
72+ for ( const [ sym , value ] of Object . entries ( selected ) ) {
73+ const idx = symbols . indexOf ( sym ) ;
74+ if ( idx > 0 ) atomic [ idx ] = value ;
75+ }
76+ return atomic ;
77+ } , [ selected ] ) ;
78+
79+ // ----------------------------
80+ // handle updates from web component
81+ // ----------------------------
82+ const handleChange = ( atomicState ) => {
83+ const mapped = { } ;
84+
85+ for ( const [ atomic , value ] of Object . entries ( atomicState ) ) {
86+ const sym = symbols [ Number ( atomic ) ] ;
87+ if ( ! sym ) continue ;
88+ mapped [ sym ] = value ;
89+ }
90+
91+ onSelectionChange ?. ( mapped ) ;
92+ } ;
9193
92- const ptable = cachedPTable [ providerUrl ] ;
94+ // ----------------------------
95+ // noHighlight from cache (IMPORTANT FIX)
96+ // ----------------------------
97+ const noHighlightArray = useMemo ( ( ) => {
98+ if ( ! providerUrl || ! cachedPTable ) return [ ] ;
9399
94- const updatedElements = allElements . map ( ( el ) => {
95- // Case 1: provider not in cache everything full color
96- if ( ! ptable ) return { ...el , present : true } ;
100+ const ptable = cachedPTable [ providerUrl ] ;
101+ if ( ! ptable ) return [ ] ;
97102
98- // Case 2: element in cache use cached value
99- if ( el . sym in ptable ) return { ...el , present : ptable [ el . sym ] } ;
103+ const out = [ ] ;
100104
101- // Case 3: element missing in provider full color
102- return { ...el , present : true } ;
103- } ) ;
105+ for ( let i = 1 ; i < symbols . length ; i ++ ) {
106+ const sym = symbols [ i ] ;
107+ if ( ptable [ sym ] === false ) out . push ( i ) ;
108+ }
104109
105- setElements ( updatedElements ) ;
110+ return out ;
106111 } , [ providerUrl , cachedPTable ] ) ;
107112
108- const toggle = ( sym ) => {
109- if ( ! onSelectionChange ) return ;
110- const newState = ( ( selected [ sym ] ?? 0 ) + 1 ) % 3 ;
111- onSelectionChange ( { [ sym ] : newState } ) ;
112- } ;
113-
114- const getColorClass = ( el ) => {
115- const state = selected [ el . sym ] ?? 0 ;
116- const baseColor = colors [ el . group ] ?? colors . default ;
117-
118- if ( state === 1 ) return selectedClassName ;
119- if ( state === 2 ) return deselectedClassName ;
120-
121- // Only grey out elements explicitly marked as absent (false)
122- const opacityClass = el . present === false ? "opacity-30" : "" ;
123-
124- return `${ baseColor } ${ defaultBorderClassName } ${ opacityClass } ` ;
125- } ;
113+ const noInteractArray = [ ] ;
126114
115+ // ----------------------------
116+ // render
117+ // ----------------------------
127118 return (
128- < div className = "w-full max-w-full mx-auto px-0 md:px-2" >
129- < div className = "@container" >
130- < div className = "flex justify-end pb-2 pr-1.5" >
131- < HelpIcon
132- popover = { `All OPTIMADE provider databases have been prefiltered, with unavailable structures greyed out. Last updated: ${ lastModified } .` }
133- placement = "left"
134- color = "rgb(40,40,40)"
135- />
136- </ div >
137- < div
138- className = "grid @gap-0.5 @sm:gap-1 w-full"
139- style = { { gridTemplateColumns : "repeat(18, minmax(0, 1fr))" } }
140- >
141- { elements . map ( ( el ) => (
142- < div
143- key = { el . sym }
144- style = { { gridColumn : el . col , gridRow : el . row } }
145- className = "relative w-full"
146- >
147- < div className = "w-full aspect-square" >
148- < button
149- onClick = { ( ) => toggle ( el . sym ) }
150- className = { `flex flex-col justify-center items-center w-full h-full rounded-sm ${ hoverClassName } ${ getColorClass (
151- el ,
152- ) } `}
153- >
154- < span className = "text-[0px] opacity-0 @sm:text-[0.3rem] @sm:opacity-100 @md:text-[0.5rem] @lg:text-[0.7rem] text-gray-700 leading-tight" >
155- { el . num }
156- </ span >
157- < span
158- className = "text-[0.55rem] font-medium leading-none"
159- style = { { fontSize : "clamp(0.55rem, 2.5vw, 1.25rem)" } }
160- >
161- { el . sym }
162- </ span >
163- </ button >
164- </ div >
165- </ div >
166- ) ) }
167- </ div >
119+ < div className = "@container" >
120+ < div className = "flex justify-end pb-2 pr-2.5" >
121+ < HelpIcon
122+ popover = { `All OPTIMADE provider databases have been prefiltered. Last updated: ${ lastModified } .` }
123+ placement = "left"
124+ color = "rgb(40,40,40)"
125+ />
168126 </ div >
127+
128+ < PTableWrapper
129+ noInteractArray = { noInteractArray }
130+ noHighlightArray = { noHighlightArray }
131+ selected = { selectionState }
132+ onChange = { handleChange }
133+ zoomLevel = { 1050 }
134+ />
169135 </ div >
170136 ) ;
171137}
0 commit comments