implement tooltip solution
parent
b54b871fa3
commit
a7c211a105
|
@ -12,7 +12,8 @@
|
|||
<meta property="og:image" content="" />
|
||||
<link rel="apple-touch-icon" href="icon-192.png" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<!-- app.css references the css imported into app.jsx -->
|
||||
<link rel="stylesheet" href="app.css">
|
||||
<title>CAD Tool</title>
|
||||
</head>
|
||||
|
||||
|
|
15
src/Scene.js
15
src/Scene.js
|
@ -34,6 +34,9 @@ export class Scene {
|
|||
|
||||
this.store = store;
|
||||
this.canvas = document.querySelector('#c');
|
||||
|
||||
this.rect = this.canvas.getBoundingClientRect().toJSON()
|
||||
|
||||
this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
|
||||
|
||||
const size = 1;
|
||||
|
@ -171,15 +174,12 @@ export class Scene {
|
|||
window.id = curid
|
||||
|
||||
const entries = state.treeEntries.byId
|
||||
console.log(entries)
|
||||
for (let k in entries) {
|
||||
|
||||
console.log(k)
|
||||
if (k[0] == 's') {
|
||||
|
||||
entries[k].obj3d = loader.parse(entries[k].obj3d)
|
||||
this.obj3d.add(entries[k].obj3d)
|
||||
entries[k] = new Sketch(this.camera, this.canvas, this.store, state.treeEntries.byId[k])
|
||||
entries[k] = new Sketch(this, state.treeEntries.byId[k])
|
||||
entries[k].obj3d.addEventListener('change', this.render) // !! took 3 hours to realize
|
||||
|
||||
} else if (k[0] == 'm') {
|
||||
|
@ -219,6 +219,9 @@ function render() {
|
|||
this.camera.left = -canvas.clientWidth / canvas.clientHeight;
|
||||
this.camera.right = canvas.clientWidth / canvas.clientHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
Object.assign(this.rect, this.canvas.getBoundingClientRect().toJSON())
|
||||
|
||||
}
|
||||
this.renderer.render(this.obj3d, this.camera);
|
||||
|
||||
|
@ -268,13 +271,13 @@ async function addSketch() {
|
|||
if (!references) return;
|
||||
|
||||
if (references[0].userData.type == 'plane') {
|
||||
sketch = new Sketch(this.camera, this.canvas, this.store)
|
||||
sketch = new Sketch(this)
|
||||
sketch.obj3d.matrix = references[0].matrix
|
||||
sketch.plane.applyMatrix4(sketch.obj3d.matrix)
|
||||
sketch.obj3d.inverse = sketch.obj3d.matrix.clone().invert()
|
||||
this.obj3d.add(sketch.obj3d)
|
||||
} else {
|
||||
sketch = new Sketch(this.camera, this.canvas, this.store)
|
||||
sketch = new Sketch(this)
|
||||
this.obj3d.add(sketch.obj3d)
|
||||
sketch.align(
|
||||
...references.map(
|
||||
|
|
|
@ -17,7 +17,7 @@ import { drawDimension, _onMoveDimension, setDimLines, updateDim } from './drawD
|
|||
class Sketch {
|
||||
|
||||
|
||||
constructor(camera, canvas, store, preload) {
|
||||
constructor(scene, preload) {
|
||||
|
||||
|
||||
// [0]:x, [1]:y, [2]:z
|
||||
|
@ -75,9 +75,12 @@ class Sketch {
|
|||
this.plane.applyMatrix4(this.obj3d.matrix)
|
||||
}
|
||||
|
||||
this.camera = camera;
|
||||
this.canvas = canvas;
|
||||
this.store = store;
|
||||
|
||||
this.scene = scene;
|
||||
this.camera = scene.camera
|
||||
this.canvas = scene.canvas
|
||||
this.rect = scene.rect
|
||||
this.store = scene.store;
|
||||
|
||||
|
||||
|
||||
|
@ -348,14 +351,16 @@ class Sketch {
|
|||
}
|
||||
}
|
||||
getLocation(e) {
|
||||
|
||||
raycaster.setFromCamera(
|
||||
_vec2.set(
|
||||
(e.clientX / window.innerWidth) * 2 - 1,
|
||||
- (e.clientY / window.innerHeight) * 2 + 1
|
||||
(e.clientX - this.rect.left)/ this.rect.width * 2 - 1,
|
||||
- (e.clientY - this.rect.top)/ this.rect.height * 2 + 1
|
||||
),
|
||||
this.camera
|
||||
);
|
||||
|
||||
|
||||
raycaster.ray.intersectPlane(this.plane, _vec3).applyMatrix4(this.obj3d.inverse)
|
||||
|
||||
return _vec3
|
||||
|
|
|
@ -6,12 +6,13 @@ export function onHover(e) {
|
|||
|
||||
raycaster.setFromCamera(
|
||||
new THREE.Vector2(
|
||||
(e.clientX / window.innerWidth) * 2 - 1,
|
||||
- (e.clientY / window.innerHeight) * 2 + 1
|
||||
(e.clientX - this.rect.left)/ this.rect.width * 2 - 1,
|
||||
- (e.clientY - this.rect.top)/ this.rect.height * 2 + 1
|
||||
),
|
||||
this.camera
|
||||
);
|
||||
|
||||
|
||||
let hoverPts;
|
||||
let idx = []
|
||||
|
||||
|
@ -43,7 +44,7 @@ export function onHover(e) {
|
|||
|
||||
if (hoverPts.length) {
|
||||
|
||||
console.log(hoverPts)
|
||||
// console.log(hoverPts)
|
||||
// for (let i = 0; i < hoverPts.length; i++) {
|
||||
// const obj = hoverPts[i].object
|
||||
// if (['point', 'plane'].includes(obj.userData.type)) {
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
/* @tailwind base; */
|
||||
@tailwind utilities;
|
||||
|
||||
/* html, */
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
--topNavH: 36px;
|
||||
--topNavH: 48px;
|
||||
--sideNavW: 200px;
|
||||
}
|
||||
|
||||
|
@ -45,3 +51,26 @@ body {
|
|||
bg-gray-100 text-green-600
|
||||
hover:bg-gray-200 hover:text-green-700;
|
||||
}
|
||||
|
||||
|
||||
.tooltip {
|
||||
position: fixed;
|
||||
display: block;
|
||||
background-color: black;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
visibility: hidden;
|
||||
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
border: solid 6px transparent;
|
||||
border-bottom-color: black;
|
||||
border-top: none;
|
||||
}
|
|
@ -4,28 +4,27 @@ import React from 'react'
|
|||
|
||||
import { createStore, applyMiddleware} from 'redux'
|
||||
import { Provider } from 'react-redux'
|
||||
import { reducer, preloadedState } from './reducer'
|
||||
import logger from 'redux-logger'
|
||||
|
||||
|
||||
import { Tree } from './treeEntry'
|
||||
import { Tree } from './tree'
|
||||
import { NavBar } from './navBar'
|
||||
import { reducer, preloadedState } from './reducer'
|
||||
import { ToolTip} from './toolTip'
|
||||
|
||||
import './app.css'
|
||||
|
||||
|
||||
window.store = createStore(reducer, preloadedState, applyMiddleware(logger))
|
||||
|
||||
const store = createStore(reducer, preloadedState, applyMiddleware(logger))
|
||||
|
||||
const App = ({ store }) => (
|
||||
<Provider store={store}>
|
||||
<NavBar />
|
||||
<Tree />
|
||||
<ToolTip/>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
ReactDOM.render(<App store={store} />, document.getElementById('react'));
|
||||
});
|
||||
|
||||
window.store = store
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
import React, { useEffect, useReducer} from 'react';
|
||||
import React, { useEffect, useReducer } from 'react';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
|
@ -58,15 +58,12 @@ export const NavBar = () => {
|
|||
|
||||
const [_, forceUpdate] = useReducer(x => x + 1, 0);
|
||||
|
||||
return <div className='topNav flex justify-center'>
|
||||
return <div className='topNav flex justify-center items-center'>
|
||||
{
|
||||
btnz.map(([Icon, fcn, txt], idx) => (
|
||||
<div className="btn flex items-center justify-start p-1 text-lg" key={idx}
|
||||
onClick={fcn}
|
||||
>
|
||||
<Icon className="w-6 h-6" />
|
||||
<div className="ml-2" tooltip="wtf">{txt}</div>
|
||||
</div>
|
||||
<Icon className="btn w-auto h-full p-2" tooltip={txt}
|
||||
onClick={fcn} key={idx}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
|
@ -11,6 +11,9 @@ export const preloadedState = {
|
|||
tree: {},
|
||||
order: {},
|
||||
},
|
||||
ui: {
|
||||
toolTipImmediate: false
|
||||
}
|
||||
}
|
||||
|
||||
export function reducer(state = {}, action) {
|
|
@ -0,0 +1,86 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export const ToolTip = () => {
|
||||
/**
|
||||
* Fires when new element is mouseovered, checks if it has a tooltip attribute
|
||||
* If it does, updates and unhides tooltip element after a preset timeout.
|
||||
* The timout is reset if user moves off of the tooltipped element
|
||||
*
|
||||
* Unfortunately, new mouseover fires for svg children, which clears the
|
||||
* tooltip state. We add hacky lines labelled svg workaround to bubbleup / ignore
|
||||
* svg children mouseovers. We use prevTooltip ref check if new svg
|
||||
* child mouseover is novel. If it's not, we ignore the event
|
||||
*/
|
||||
|
||||
const [state, setState] = useState(null)
|
||||
|
||||
const ref = useRef()
|
||||
|
||||
const activated = useRef(false)
|
||||
const timeout = useRef(null)
|
||||
|
||||
const prevTooltip = useRef(null) // svg workaround
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const svgChildren = ['path', 'g', 'rect', 'circle']; // svg workaround
|
||||
|
||||
document.addEventListener('mouseover', (e) => {
|
||||
let node = e.target;
|
||||
|
||||
while (svgChildren.includes(node.nodeName)) { // svg workaround
|
||||
node = node.parentElement // svg workaround
|
||||
} // svg workaround
|
||||
|
||||
const tooltip = node.getAttribute("tooltip")
|
||||
|
||||
if (tooltip == prevTooltip.current) return // svg workaround
|
||||
prevTooltip.current = tooltip // svg workaround
|
||||
|
||||
clearTimeout(timeout.current)
|
||||
|
||||
if (tooltip) {
|
||||
let { left, top, width, height } = node.getBoundingClientRect()
|
||||
left = left + width / 2 - getTextWidth(tooltip) / 2 - 4 // 4 is padding
|
||||
top = top + height + 6 // 6 is arrow height/width
|
||||
|
||||
setState(tooltip)
|
||||
|
||||
if (activated.current) {
|
||||
ref.current.setAttribute('style', `left:${left}px; top:${top}px; visibility:visible`)
|
||||
} else {
|
||||
timeout.current = setTimeout(() => {
|
||||
ref.current.setAttribute('style', `left:${left}px; top:${top}px; visibility:visible`)
|
||||
activated.current = true
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
ref.current.setAttribute('style', `visibility:hidden`)
|
||||
activated.current = false
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
}, [])
|
||||
|
||||
|
||||
return <div className="tooltip" ref={ref}>
|
||||
{state}
|
||||
<div className="arrow"></div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getTextWidth(text, font = "16px sans-serif") {
|
||||
// re-use canvas object for better performance
|
||||
let canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
|
||||
let context = canvas.getContext("2d");
|
||||
context.font = font;
|
||||
let metrics = context.measureText(text);
|
||||
return metrics.width;
|
||||
}
|
|
@ -4,7 +4,7 @@ const tailwindcss = require('tailwindcss')
|
|||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './src/app.jsx',
|
||||
app: './src/react/app.jsx',
|
||||
scene: './src/Scene.js',
|
||||
},
|
||||
output: {
|
||||
|
|
Loading…
Reference in New Issue