commit
c077c9103d
8 changed files with 358 additions and 147 deletions
|
@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|||
* New viewer for human-readable text files ([#1826](https://github.com/lbryio/lbry-desktop/pull/1826))
|
||||
* CSV and JSON viewer ([#1410](https://github.com/lbryio/lbry-desktop/pull/1410))
|
||||
* Recommended content on file viewer page ([#1845](https://github.com/lbryio/lbry-desktop/pull/1845))
|
||||
* 3D File viewer improvements ([#1870](https://github.com/lbryio/lbry-desktop/pull/1870))
|
||||
* Desktop notification when publish is completed ([#1892](https://github.com/lbryio/lbry-desktop/pull/1892))
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"classnames": "^2.2.5",
|
||||
"codemirror": "^5.39.2",
|
||||
"country-data": "^0.0.31",
|
||||
"dat.gui": "^0.7.2",
|
||||
"dom-scroll-into-view": "^1.2.1",
|
||||
"electron-dl": "^1.11.0",
|
||||
"electron-is-dev": "^0.3.0",
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import * as dat from 'dat.gui';
|
||||
import classNames from 'classnames';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
|
||||
// ThreeJS
|
||||
import * as THREE from './internal/three';
|
||||
import detectWebGL from './internal/detector';
|
||||
|
@ -11,123 +14,39 @@ import ThreeRenderer from './internal/renderer';
|
|||
|
||||
type Props = {
|
||||
theme: string,
|
||||
autoRotate: boolean,
|
||||
source: {
|
||||
fileType: string,
|
||||
downloadPath: string,
|
||||
},
|
||||
};
|
||||
|
||||
class ThreeViewer extends React.PureComponent<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
type State = {
|
||||
error: ?string,
|
||||
isReady: boolean,
|
||||
isLoading: boolean,
|
||||
};
|
||||
|
||||
const { theme } = this.props;
|
||||
class ThreeViewer extends React.PureComponent<Props, State> {
|
||||
static testWebgl = new Promise((resolve, reject) => {
|
||||
if (detectWebGL()) resolve();
|
||||
else reject();
|
||||
});
|
||||
|
||||
// Main container
|
||||
this.viewer = React.createRef();
|
||||
|
||||
// Object colors
|
||||
this.materialColors = {
|
||||
red: '#e74c3c',
|
||||
blue: '#3498db',
|
||||
green: '#44b098',
|
||||
orange: '#f39c12',
|
||||
};
|
||||
|
||||
// Viewer themes
|
||||
this.themes = {
|
||||
dark: {
|
||||
gridColor: '#414e5c',
|
||||
groundColor: '#13233C',
|
||||
backgroundColor: '#13233C',
|
||||
centerLineColor: '#7f8c8d',
|
||||
},
|
||||
light: {
|
||||
gridColor: '#7f8c8d',
|
||||
groundColor: '#DDD',
|
||||
backgroundColor: '#EEE',
|
||||
centerLineColor: '#2F2F2F',
|
||||
},
|
||||
};
|
||||
|
||||
// Select current theme
|
||||
this.theme = this.themes[theme] || this.themes.light;
|
||||
|
||||
// State
|
||||
this.state = {
|
||||
error: null,
|
||||
isReady: false,
|
||||
isLoading: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (detectWebGL()) {
|
||||
this.renderScene();
|
||||
// Update render on resize window
|
||||
window.addEventListener('resize', this.handleResize, false);
|
||||
} else {
|
||||
// No webgl support, handle Error...
|
||||
// TODO: Use a better error message
|
||||
this.setState({ error: "Sorry, your computer doesn't support WebGL." });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize, false);
|
||||
}
|
||||
|
||||
transformGroup(group) {
|
||||
this.fitMeshToCamera(group);
|
||||
this.createWireFrame(group);
|
||||
this.updateControlsTarget(group.position);
|
||||
}
|
||||
|
||||
createOrbitControls(camera, canvas) {
|
||||
const { autoRotate } = this.props;
|
||||
static createOrbitControls(camera, canvas) {
|
||||
const controls = new THREE.OrbitControls(camera, canvas);
|
||||
// Controls configuration
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.75;
|
||||
controls.enableZoom = true;
|
||||
controls.minDistance = 1;
|
||||
controls.maxDistance = 50;
|
||||
controls.autoRotate = autoRotate;
|
||||
controls.minDistance = 5;
|
||||
controls.maxDistance = 14;
|
||||
controls.autoRotate = false;
|
||||
controls.enablePan = false;
|
||||
controls.saveState();
|
||||
return controls;
|
||||
}
|
||||
|
||||
createGeometry(data) {
|
||||
const geometry = new THREE.Geometry();
|
||||
geometry.fromBufferGeometry(data);
|
||||
geometry.computeBoundingBox();
|
||||
geometry.computeVertexNormals();
|
||||
geometry.center();
|
||||
geometry.rotateX(-Math.PI / 2);
|
||||
geometry.lookAt(new THREE.Vector3(0, 0, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
createWireFrame(group) {
|
||||
const wireframeGeometry = new THREE.WireframeGeometry(group.geometry);
|
||||
const wireframeMaterial = new THREE.LineBasicMaterial({
|
||||
opacity: 0,
|
||||
transparent: true,
|
||||
linewidth: 1,
|
||||
});
|
||||
// Set material color
|
||||
wireframeMaterial.color.set(this.materialColors.green);
|
||||
this.wireframe = new THREE.LineSegments(wireframeGeometry, wireframeMaterial);
|
||||
group.add(this.wireframe);
|
||||
}
|
||||
|
||||
toggleWireFrame(show = false) {
|
||||
this.wireframe.opacity = show ? 1 : 0;
|
||||
this.mesh.material.opacity = show ? 0 : 1;
|
||||
}
|
||||
|
||||
fitMeshToCamera(group) {
|
||||
static fitMeshToCamera(group) {
|
||||
const max = { x: 0, y: 0, z: 0 };
|
||||
const min = { x: 0, y: 0, z: 0 };
|
||||
|
||||
|
@ -147,20 +66,214 @@ class ThreeViewer extends React.PureComponent<Props> {
|
|||
|
||||
const meshY = Math.abs(max.y - min.y);
|
||||
const meshX = Math.abs(max.x - min.x);
|
||||
|
||||
const scaleFactor = 10 / Math.max(meshX, meshY);
|
||||
|
||||
group.scale.set(scaleFactor, scaleFactor, scaleFactor);
|
||||
group.position.setY((meshY / 2) * scaleFactor);
|
||||
|
||||
// Reset object position
|
||||
const box = new THREE.Box3().setFromObject(group);
|
||||
// Update position
|
||||
box.getCenter(group.position);
|
||||
|
||||
group.position.multiplyScalar(-1);
|
||||
group.position.setY(group.position.y + meshY * scaleFactor);
|
||||
}
|
||||
|
||||
/*
|
||||
See: https://github.com/mrdoob/three.js/blob/dev/docs/scenes/js/material.js#L195
|
||||
*/
|
||||
|
||||
static updateMaterial(material, geometry) {
|
||||
material.vertexColors = +material.vertexColors; // Ensure number
|
||||
material.side = +material.side; // Ensure number
|
||||
material.needsUpdate = true;
|
||||
// If Geometry needs update
|
||||
if (geometry) {
|
||||
geometry.verticesNeedUpdate = true;
|
||||
geometry.normalsNeedUpdate = true;
|
||||
geometry.colorsNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const { theme } = this.props;
|
||||
this.viewer = React.createRef();
|
||||
this.guiContainer = React.createRef();
|
||||
// Object defualt color
|
||||
this.materialColor = '#44b098';
|
||||
// Viewer themes
|
||||
this.themes = {
|
||||
dark: {
|
||||
gridColor: '#414e5c',
|
||||
groundColor: '#13233C',
|
||||
backgroundColor: '#13233C',
|
||||
centerLineColor: '#7f8c8d',
|
||||
},
|
||||
light: {
|
||||
gridColor: '#7f8c8d',
|
||||
groundColor: '#DDD',
|
||||
backgroundColor: '#EEE',
|
||||
centerLineColor: '#2F2F2F',
|
||||
},
|
||||
};
|
||||
// Select current theme
|
||||
this.theme = this.themes[theme] || this.themes.light;
|
||||
// State
|
||||
this.state = {
|
||||
error: null,
|
||||
isReady: false,
|
||||
isLoading: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
ThreeViewer.testWebgl
|
||||
.then(() => {
|
||||
this.renderScene();
|
||||
// Update render on resize window
|
||||
window.addEventListener('resize', this.handleResize, false);
|
||||
})
|
||||
.catch(() => {
|
||||
// No webgl support, handle Error...
|
||||
// TODO: Use a better error message
|
||||
this.setState({ error: "Sorry, your computer doesn't support WebGL." });
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Remove event listeners
|
||||
window.removeEventListener('resize', this.handleResize, false);
|
||||
// Free memory
|
||||
if (this.renderer && this.mesh) {
|
||||
// Clean up group
|
||||
this.scene.remove(this.mesh);
|
||||
if (this.mesh.geometry) this.mesh.geometry.dispose();
|
||||
if (this.mesh.material) this.mesh.material.dispose();
|
||||
// Cleanup shared geometry
|
||||
if (this.geometry) this.geometry.dispose();
|
||||
if (this.bufferGeometry) this.bufferGeometry.dispose();
|
||||
// Clean up shared material
|
||||
if (this.material) this.material.dispose();
|
||||
// Clean up grid
|
||||
if (this.grid) {
|
||||
this.grid.material.dispose();
|
||||
this.grid.geometry.dispose();
|
||||
}
|
||||
// Clean up group items
|
||||
this.mesh.traverse(child => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
if (child.geometry) child.geometry.dispose();
|
||||
if (child.material) child.material.dispose();
|
||||
}
|
||||
});
|
||||
// Clean up controls
|
||||
if (this.controls) this.controls.dispose();
|
||||
// It's unclear if we need this:
|
||||
if (this.renderer) {
|
||||
this.renderer.renderLists.dispose();
|
||||
this.renderer.dispose();
|
||||
}
|
||||
// Stop animation
|
||||
cancelAnimationFrame(this.frameID);
|
||||
// Destroy GUI Controls
|
||||
if (this.gui) this.gui.destroy();
|
||||
// Empty objects
|
||||
this.grid = null;
|
||||
this.mesh = null;
|
||||
this.renderer = null;
|
||||
this.material = null;
|
||||
this.geometry = null;
|
||||
this.bufferGeometry = null;
|
||||
}
|
||||
}
|
||||
|
||||
transformGroup(group) {
|
||||
ThreeViewer.fitMeshToCamera(group);
|
||||
|
||||
if (!this.targetCenter) {
|
||||
const box = new THREE.Box3();
|
||||
this.targetCenter = box.setFromObject(this.mesh).getCenter();
|
||||
}
|
||||
this.updateControlsTarget(this.targetCenter);
|
||||
}
|
||||
|
||||
createInterfaceControls() {
|
||||
if (this.guiContainer && this.mesh) {
|
||||
this.gui = new dat.GUI({ autoPlace: false, name: 'controls' });
|
||||
|
||||
const config = {
|
||||
color: this.materialColor,
|
||||
};
|
||||
|
||||
config.reset = () => {
|
||||
// Reset material color
|
||||
config.color = this.materialColor;
|
||||
// Reset material
|
||||
this.material.color.set(config.color);
|
||||
this.material.flatShading = true;
|
||||
this.material.shininess = 30;
|
||||
this.material.wireframe = false;
|
||||
// Reset autoRotate
|
||||
this.controls.autoRotate = false;
|
||||
// Reset camera
|
||||
this.restoreCamera();
|
||||
};
|
||||
|
||||
const materialFolder = this.gui.addFolder('Material');
|
||||
|
||||
// Color picker
|
||||
const colorPicker = materialFolder
|
||||
.addColor(config, 'color')
|
||||
.name('Color')
|
||||
.listen();
|
||||
|
||||
colorPicker.onChange(color => {
|
||||
this.material.color.set(color);
|
||||
});
|
||||
|
||||
materialFolder
|
||||
.add(this.material, 'shininess', 0, 100)
|
||||
.name('Shininess')
|
||||
.listen();
|
||||
|
||||
materialFolder
|
||||
.add(this.material, 'flatShading')
|
||||
.name('FlatShading')
|
||||
.onChange(() => {
|
||||
ThreeViewer.updateMaterial(this.material);
|
||||
})
|
||||
.listen();
|
||||
|
||||
materialFolder
|
||||
.add(this.material, 'wireframe')
|
||||
.name('Wireframe')
|
||||
.listen();
|
||||
|
||||
const sceneFolder = this.gui.addFolder('Scene');
|
||||
|
||||
sceneFolder
|
||||
.add(this.controls, 'autoRotate')
|
||||
.name('Auto-Rotate')
|
||||
.listen();
|
||||
|
||||
sceneFolder.add(config, 'reset').name('Reset');
|
||||
|
||||
this.guiContainer.current.appendChild(this.gui.domElement);
|
||||
}
|
||||
}
|
||||
|
||||
createGeometry(data) {
|
||||
this.bufferGeometry = data;
|
||||
this.bufferGeometry.computeBoundingBox();
|
||||
this.bufferGeometry.center();
|
||||
this.bufferGeometry.rotateX(-Math.PI / 2);
|
||||
this.bufferGeometry.lookAt(new THREE.Vector3(0, 0, 1));
|
||||
// Get geometry from bufferGeometry
|
||||
this.geometry = new THREE.Geometry().fromBufferGeometry(this.bufferGeometry);
|
||||
this.geometry.mergeVertices();
|
||||
this.geometry.computeVertexNormals();
|
||||
}
|
||||
|
||||
startLoader() {
|
||||
const { source } = this.props;
|
||||
|
||||
|
@ -179,6 +292,8 @@ class ThreeViewer extends React.PureComponent<Props> {
|
|||
|
||||
handleReady = () => {
|
||||
this.setState({ isReady: true, isLoading: false });
|
||||
// GUI
|
||||
this.createInterfaceControls();
|
||||
};
|
||||
|
||||
handleError = () => {
|
||||
|
@ -193,32 +308,29 @@ class ThreeViewer extends React.PureComponent<Props> {
|
|||
this.renderer.setSize(width, height);
|
||||
};
|
||||
|
||||
handleColorChange(color) {
|
||||
if (!this.mesh) return;
|
||||
const pickColor = this.materialColors[color] || this.materialColors.green;
|
||||
this.mesh.material.color.set(pickColor);
|
||||
this.wireframe.material.color.set(pickColor);
|
||||
}
|
||||
|
||||
updateControlsTarget(point) {
|
||||
this.controls.target.fromArray([point.x, point.y, point.z]);
|
||||
this.controls.update();
|
||||
}
|
||||
|
||||
restoreCamera() {
|
||||
this.controls.reset();
|
||||
this.camera.position.set(-9.5, 14, 11);
|
||||
this.updateControlsTarget(this.targetCenter);
|
||||
}
|
||||
|
||||
renderStl(data) {
|
||||
const geometry = this.createGeometry(data);
|
||||
const group = new THREE.Mesh(geometry, this.material);
|
||||
// Assign name
|
||||
group.name = 'objectGroup';
|
||||
this.scene.add(group);
|
||||
this.transformGroup(group);
|
||||
this.mesh = group;
|
||||
this.createGeometry(data);
|
||||
this.mesh = new THREE.Mesh(this.geometry, this.material);
|
||||
this.mesh.name = 'model';
|
||||
this.scene.add(this.mesh);
|
||||
this.transformGroup(this.mesh);
|
||||
}
|
||||
|
||||
renderObj(event) {
|
||||
const mesh = event.detail.loaderRootNode;
|
||||
const group = new THREE.Group();
|
||||
group.name = 'objGroup';
|
||||
this.mesh = new THREE.Group();
|
||||
this.mesh.name = 'model';
|
||||
|
||||
// Assign new material
|
||||
mesh.traverse(child => {
|
||||
|
@ -226,15 +338,18 @@ class ThreeViewer extends React.PureComponent<Props> {
|
|||
// Get geometry from child
|
||||
const geometry = new THREE.Geometry();
|
||||
geometry.fromBufferGeometry(child.geometry);
|
||||
geometry.mergeVertices();
|
||||
geometry.computeVertexNormals();
|
||||
// Create and regroup inner objects
|
||||
const innerObj = new THREE.Mesh(geometry, this.material);
|
||||
group.add(innerObj);
|
||||
this.mesh.add(innerObj);
|
||||
// Clean up geometry
|
||||
geometry.dispose();
|
||||
child.geometry.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
this.scene.add(group);
|
||||
this.transformGroup(group);
|
||||
this.mesh = group;
|
||||
this.scene.add(this.mesh);
|
||||
this.transformGroup(this.mesh);
|
||||
}
|
||||
|
||||
renderModel(fileType, parsedData) {
|
||||
|
@ -254,6 +369,7 @@ class ThreeViewer extends React.PureComponent<Props> {
|
|||
this.renderer = ThreeRenderer({
|
||||
antialias: true,
|
||||
shadowMap: true,
|
||||
gammaCorrection: true,
|
||||
});
|
||||
|
||||
this.scene = ThreeScene({
|
||||
|
@ -268,54 +384,56 @@ class ThreeViewer extends React.PureComponent<Props> {
|
|||
// Grid
|
||||
this.grid = ThreeGrid({ size: 100, gridColor, centerLineColor });
|
||||
this.scene.add(this.grid);
|
||||
|
||||
// Camera
|
||||
this.camera = new THREE.PerspectiveCamera(80, width / height, 0.1, 1000);
|
||||
this.camera.position.set(-9.5, 14, 11);
|
||||
|
||||
// Controls
|
||||
this.controls = this.createOrbitControls(this.camera, canvas);
|
||||
this.controls = ThreeViewer.createOrbitControls(this.camera, canvas);
|
||||
|
||||
// Set viewer size
|
||||
this.renderer.setSize(width, height);
|
||||
|
||||
// Create model material
|
||||
this.material = new THREE.MeshPhongMaterial({
|
||||
opacity: 1,
|
||||
transparent: true,
|
||||
// depthWrite: true,
|
||||
depthWrite: true,
|
||||
flatShading: true,
|
||||
vertexColors: THREE.FaceColors,
|
||||
// Positive value pushes polygon further away
|
||||
// polygonOffsetFactor: 1,
|
||||
// polygonOffsetUnits: 1,
|
||||
});
|
||||
|
||||
// Set material color
|
||||
this.material.color.set(this.materialColors.green);
|
||||
this.material.color.set(this.materialColor);
|
||||
|
||||
// Load file and render mesh
|
||||
this.startLoader();
|
||||
|
||||
// Append canvas
|
||||
viewer.appendChild(canvas);
|
||||
|
||||
const updateScene = () => {
|
||||
requestAnimationFrame(updateScene);
|
||||
this.controls.update();
|
||||
this.frameID = requestAnimationFrame(updateScene);
|
||||
if (this.controls.autoRotate) this.controls.update();
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
};
|
||||
|
||||
updateScene();
|
||||
// Append canvas
|
||||
viewer.appendChild(canvas);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { theme } = this.props;
|
||||
const { error, isReady, isLoading } = this.state;
|
||||
const loadingMessage = 'Loading 3D model.';
|
||||
const loadingMessage = __('Loading 3D model.');
|
||||
const showViewer = isReady && !error;
|
||||
const showLoading = isLoading && !error;
|
||||
|
||||
// Adaptive theme for gui controls
|
||||
const containerClass = classNames('gui-container', { light: theme === 'light' });
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{error && <LoadingScreen status={error} spinner={false} />}
|
||||
{showLoading && <LoadingScreen status={loadingMessage} spinner />}
|
||||
<div ref={this.guiContainer} className={containerClass} />
|
||||
<div
|
||||
style={{ opacity: showViewer ? 1 : 0 }}
|
||||
className="three-viewer file-render__viewer"
|
||||
|
|
|
@ -3,10 +3,8 @@ import { GridHelper, Color } from './three';
|
|||
const ThreeGrid = ({ size, gridColor, centerLineColor }) => {
|
||||
const divisions = size / 2;
|
||||
const grid = new GridHelper(size, divisions, new Color(centerLineColor), new Color(gridColor));
|
||||
|
||||
grid.material.opacity = 0.4;
|
||||
grid.material.transparent = true;
|
||||
|
||||
return grid;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { WebGLRenderer } from './three';
|
||||
|
||||
const ThreeRenderer = ({ antialias, shadowMap }) => {
|
||||
const renderer = new WebGLRenderer({
|
||||
antialias,
|
||||
});
|
||||
const ThreeRenderer = ({ antialias, shadowMap, gammaCorrection }) => {
|
||||
const renderer = new WebGLRenderer({ antialias });
|
||||
// Renderer configuration
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.gammaInput = true;
|
||||
renderer.gammaOutput = true;
|
||||
renderer.shadowMap.enabled = shadowMap;
|
||||
renderer.gammaInput = gammaCorrection || false;
|
||||
renderer.gammaOutput = gammaCorrection || false;
|
||||
renderer.shadowMap.enabled = shadowMap || false;
|
||||
renderer.shadowMap.autoUpdate = false;
|
||||
return renderer;
|
||||
};
|
||||
|
||||
|
|
|
@ -28,3 +28,4 @@
|
|||
@import 'component/_search.scss';
|
||||
@import 'component/_toggle.scss';
|
||||
@import 'component/_search.scss';
|
||||
@import 'component/_dat-gui.scss';
|
||||
|
|
89
src/renderer/scss/component/_dat-gui.scss
Normal file
89
src/renderer/scss/component/_dat-gui.scss
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* dat.gui component
|
||||
*/
|
||||
|
||||
.gui-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
|
||||
.dg.main {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Light theme:
|
||||
* https://github.com/liabru/dat-gui-light-theme
|
||||
*/
|
||||
|
||||
.gui-container.light {
|
||||
.dg.main.taller-than-window .close-button {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.dg.main .close-button {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.dg.main .close-button:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.dg {
|
||||
color: #555;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.dg.main::-webkit-scrollbar {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.dg.main::-webkit-scrollbar-thumb {
|
||||
background: #bbb;
|
||||
}
|
||||
|
||||
.dg li:not(.folder) {
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.dg li.save-row .button {
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.dg li.title {
|
||||
background: #e8e8e8
|
||||
url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==)
|
||||
6px 10px no-repeat;
|
||||
}
|
||||
|
||||
.dg .cr.function:hover,
|
||||
.dg .cr.boolean:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.dg .c input[type='text'] {
|
||||
background: #e9e9e9;
|
||||
}
|
||||
|
||||
.dg .c input[type='text']:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.dg .c input[type='text']:focus {
|
||||
background: #eee;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.dg .c .slider {
|
||||
background: #e9e9e9;
|
||||
}
|
||||
|
||||
.dg .c .slider:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
|
@ -2514,6 +2514,10 @@ dashdash@^1.12.0:
|
|||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
dat.gui@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/dat.gui/-/dat.gui-0.7.2.tgz#57056f286d0f989e83f5adec196f24fd69c01ae3"
|
||||
|
||||
date-fns@^1.27.2:
|
||||
version "1.29.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
|
||||
|
|
Loading…
Add table
Reference in a new issue