spacedb/client/components/SearchAndVisualize.jsx
2025-12-08 15:12:52 -05:00

201 lines
5.1 KiB
JavaScript

import React from 'react';
import PropTypes from 'react-proptypes';
import AsyncSelect from 'react-select/lib/Async';
import { parseQuery } from '../util';
const INPUT_LENGTH_MIN = 3;
const loadOptions = (inputValue, callback) => {
return new Promise(resolve => {
if (inputValue.length < INPUT_LENGTH_MIN) {
resolve([]);
return;
}
fetch(`/api/objects/search?q=${inputValue}`).then(resp => {
return resp.json()
}).then(respJson => {
resolve(respJson.results.map(result => {
return {
label: result.fullname,
vizLabel: result.name,
value: result.slug,
ephem: result.ephem,
};
}));
});
});
};
class SearchAndVisualize extends React.Component {
constructor(props) {
super(props);
this._objCount = 0;
this.state = {
inputValue: '',
savedDate: undefined,
selectedObjects: [],
};
}
componentDidMount() {
const hash = parseQuery(window.location.hash);
if (hash.ob) {
fetch(`/api/objects?slugs=${hash.ob}`).then(resp => {
return resp.json()
}).then(respJson => {
this.addMany(respJson.results.map(result => {
return {
label: result.fullname,
vizLabel: result.name,
value: result.slug,
ephem: result.ephem,
};
}), {
showLabel: true,
showOrbit: true,
updateHash: true,
});
});
}
if (hash.cat) {
const query = parseQuery(window.location.search);
const limit = query.limit || 3000;
fetch(`/api/category/${hash.cat}?limit=${limit}`).then(resp => {
return resp.json()
}).then(respJson => {
this.addMany(respJson.data.map(result => {
return {
label: result.fullname,
vizLabel: result.name,
value: result.slug,
ephem: result.ephem,
};
}), {
showLabel: false,
showOrbit: false,
updateHash: false,
displayEclipticLines: false,
});
});
}
if (hash.date) {
const dateObj = new Date(Date.parse(`${hash.date}T00:00:00Z`));
window.viz.setDate(dateObj);
this.setState({
savedDate: hash.date,
});
}
}
addMany(objs, opts) {
opts = opts || {};
objs.forEach(obj => {
const key = `spaceobject${this._objCount++}`;
window.viz.createObject(key, Object.assign(window.VIZ_OBJECT_OPTS, {
ephem: new Spacekit.Ephem(obj.ephem, 'deg'),
// Show short name
labelText: opts.showLabel ? obj.vizLabel : undefined,
hideOrbit: !opts.showOrbit,
particleSize: opts.particleSize,
ecliptic: {
displayLines: opts.displayEclipticLines,
},
}));
});
if (opts.updateHash) {
this.setState(prevState => ({ selectedObjects: prevState.selectedObjects.concat(objs) }), () => {
let hashStr = '#ob=' + this.state.selectedObjects.map(object => object.value).join(',');
if (this.state.savedDate) {
hashStr += `&date=${this.state.savedDate}`;
}
window.location.hash = hashStr;
});
}
}
handleChange(inputValue) {
this.addMany([inputValue], {
showLabel: true,
showOrbit: true,
updateHash: true,
});
}
getObjectList() {
return this.state.selectedObjects.map(object => {
return (
<div className="tile" key={`selectedObject-${object.value}`}>
<a target="_blank" href={`/asteroid/${object.value}`}>
<h5>{object.label}</h5>
</a>
</div>
);
});
}
render() {
return (
<div>
<AsyncSelect
cacheOptions
loadOptions={loadOptions}
onChange={this.handleChange.bind(this)}
value={null}
placeholder="Search for an asteroid or comet..."
className="topnav__react-search__control"
noOptionsMessage={() => {
if (this.state.inputValue.length < INPUT_LENGTH_MIN) {
return null;
}
return "No matching objects"
}}
styles={{
input: base => ({
...base,
color: "#fff"
}),
noOptionsMessage: base => ({
...base,
color: "#ccc"
}),
loadingMessage: base => ({
...base,
color: "#ccc"
}),
}}
theme={(theme) => ({
...theme,
borderRadius: 0,
colors: {
...theme.colors,
primary25: '#404040',
primary: 'black',
neutral0: '#1d1d1d',
neutral5: '#000a',
neutral10: '#000b',
neutral20: '#666',
neutral30: '#000d',
neutral40: '#000e',
},
})}
/>
{this.state.selectedObjects.length > 0 ? (
<div className="item-container tile-list">
{this.getObjectList()}
</div>
): null}
</div>
);
}
}
SearchAndVisualize.propTypes = {
blah: PropTypes.string,
};
export default SearchAndVisualize;