implement tooltip solution
parent
b54b871fa3
commit
a7c211a105
|
@ -12,7 +12,8 @@
|
||||||
<meta property="og:image" content="" />
|
<meta property="og:image" content="" />
|
||||||
<link rel="apple-touch-icon" href="icon-192.png" />
|
<link rel="apple-touch-icon" href="icon-192.png" />
|
||||||
<link rel="manifest" href="manifest.json" />
|
<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>
|
<title>CAD Tool</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
15
src/Scene.js
15
src/Scene.js
|
@ -34,6 +34,9 @@ export class Scene {
|
||||||
|
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.canvas = document.querySelector('#c');
|
this.canvas = document.querySelector('#c');
|
||||||
|
|
||||||
|
this.rect = this.canvas.getBoundingClientRect().toJSON()
|
||||||
|
|
||||||
this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
|
this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
|
||||||
|
|
||||||
const size = 1;
|
const size = 1;
|
||||||
|
@ -171,15 +174,12 @@ export class Scene {
|
||||||
window.id = curid
|
window.id = curid
|
||||||
|
|
||||||
const entries = state.treeEntries.byId
|
const entries = state.treeEntries.byId
|
||||||
console.log(entries)
|
|
||||||
for (let k in entries) {
|
for (let k in entries) {
|
||||||
|
|
||||||
console.log(k)
|
|
||||||
if (k[0] == 's') {
|
if (k[0] == 's') {
|
||||||
|
|
||||||
entries[k].obj3d = loader.parse(entries[k].obj3d)
|
entries[k].obj3d = loader.parse(entries[k].obj3d)
|
||||||
this.obj3d.add(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
|
entries[k].obj3d.addEventListener('change', this.render) // !! took 3 hours to realize
|
||||||
|
|
||||||
} else if (k[0] == 'm') {
|
} else if (k[0] == 'm') {
|
||||||
|
@ -219,6 +219,9 @@ function render() {
|
||||||
this.camera.left = -canvas.clientWidth / canvas.clientHeight;
|
this.camera.left = -canvas.clientWidth / canvas.clientHeight;
|
||||||
this.camera.right = canvas.clientWidth / canvas.clientHeight;
|
this.camera.right = canvas.clientWidth / canvas.clientHeight;
|
||||||
this.camera.updateProjectionMatrix();
|
this.camera.updateProjectionMatrix();
|
||||||
|
|
||||||
|
Object.assign(this.rect, this.canvas.getBoundingClientRect().toJSON())
|
||||||
|
|
||||||
}
|
}
|
||||||
this.renderer.render(this.obj3d, this.camera);
|
this.renderer.render(this.obj3d, this.camera);
|
||||||
|
|
||||||
|
@ -268,13 +271,13 @@ async function addSketch() {
|
||||||
if (!references) return;
|
if (!references) return;
|
||||||
|
|
||||||
if (references[0].userData.type == 'plane') {
|
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.obj3d.matrix = references[0].matrix
|
||||||
sketch.plane.applyMatrix4(sketch.obj3d.matrix)
|
sketch.plane.applyMatrix4(sketch.obj3d.matrix)
|
||||||
sketch.obj3d.inverse = sketch.obj3d.matrix.clone().invert()
|
sketch.obj3d.inverse = sketch.obj3d.matrix.clone().invert()
|
||||||
this.obj3d.add(sketch.obj3d)
|
this.obj3d.add(sketch.obj3d)
|
||||||
} else {
|
} else {
|
||||||
sketch = new Sketch(this.camera, this.canvas, this.store)
|
sketch = new Sketch(this)
|
||||||
this.obj3d.add(sketch.obj3d)
|
this.obj3d.add(sketch.obj3d)
|
||||||
sketch.align(
|
sketch.align(
|
||||||
...references.map(
|
...references.map(
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { drawDimension, _onMoveDimension, setDimLines, updateDim } from './drawD
|
||||||
class Sketch {
|
class Sketch {
|
||||||
|
|
||||||
|
|
||||||
constructor(camera, canvas, store, preload) {
|
constructor(scene, preload) {
|
||||||
|
|
||||||
|
|
||||||
// [0]:x, [1]:y, [2]:z
|
// [0]:x, [1]:y, [2]:z
|
||||||
|
@ -75,9 +75,12 @@ class Sketch {
|
||||||
this.plane.applyMatrix4(this.obj3d.matrix)
|
this.plane.applyMatrix4(this.obj3d.matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.camera = camera;
|
|
||||||
this.canvas = canvas;
|
this.scene = scene;
|
||||||
this.store = store;
|
this.camera = scene.camera
|
||||||
|
this.canvas = scene.canvas
|
||||||
|
this.rect = scene.rect
|
||||||
|
this.store = scene.store;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -348,14 +351,16 @@ class Sketch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getLocation(e) {
|
getLocation(e) {
|
||||||
|
|
||||||
raycaster.setFromCamera(
|
raycaster.setFromCamera(
|
||||||
_vec2.set(
|
_vec2.set(
|
||||||
(e.clientX / window.innerWidth) * 2 - 1,
|
(e.clientX - this.rect.left)/ this.rect.width * 2 - 1,
|
||||||
- (e.clientY / window.innerHeight) * 2 + 1
|
- (e.clientY - this.rect.top)/ this.rect.height * 2 + 1
|
||||||
),
|
),
|
||||||
this.camera
|
this.camera
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
raycaster.ray.intersectPlane(this.plane, _vec3).applyMatrix4(this.obj3d.inverse)
|
raycaster.ray.intersectPlane(this.plane, _vec3).applyMatrix4(this.obj3d.inverse)
|
||||||
|
|
||||||
return _vec3
|
return _vec3
|
||||||
|
|
|
@ -6,12 +6,13 @@ export function onHover(e) {
|
||||||
|
|
||||||
raycaster.setFromCamera(
|
raycaster.setFromCamera(
|
||||||
new THREE.Vector2(
|
new THREE.Vector2(
|
||||||
(e.clientX / window.innerWidth) * 2 - 1,
|
(e.clientX - this.rect.left)/ this.rect.width * 2 - 1,
|
||||||
- (e.clientY / window.innerHeight) * 2 + 1
|
- (e.clientY - this.rect.top)/ this.rect.height * 2 + 1
|
||||||
),
|
),
|
||||||
this.camera
|
this.camera
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
let hoverPts;
|
let hoverPts;
|
||||||
let idx = []
|
let idx = []
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ export function onHover(e) {
|
||||||
|
|
||||||
if (hoverPts.length) {
|
if (hoverPts.length) {
|
||||||
|
|
||||||
console.log(hoverPts)
|
// console.log(hoverPts)
|
||||||
// for (let i = 0; i < hoverPts.length; i++) {
|
// for (let i = 0; i < hoverPts.length; i++) {
|
||||||
// const obj = hoverPts[i].object
|
// const obj = hoverPts[i].object
|
||||||
// if (['point', 'plane'].includes(obj.userData.type)) {
|
// if (['point', 'plane'].includes(obj.userData.type)) {
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
/* @tailwind base; */
|
/* @tailwind base; */
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
/* html, */
|
|
||||||
|
* {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
--topNavH: 36px;
|
--topNavH: 48px;
|
||||||
--sideNavW: 200px;
|
--sideNavW: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,3 +51,26 @@ body {
|
||||||
bg-gray-100 text-green-600
|
bg-gray-100 text-green-600
|
||||||
hover:bg-gray-200 hover:text-green-700;
|
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 { createStore, applyMiddleware} from 'redux'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
|
import { reducer, preloadedState } from './reducer'
|
||||||
import logger from 'redux-logger'
|
import logger from 'redux-logger'
|
||||||
|
|
||||||
|
import { Tree } from './tree'
|
||||||
import { Tree } from './treeEntry'
|
|
||||||
import { NavBar } from './navBar'
|
import { NavBar } from './navBar'
|
||||||
import { reducer, preloadedState } from './reducer'
|
import { ToolTip} from './toolTip'
|
||||||
|
|
||||||
import './app.css'
|
import './app.css'
|
||||||
|
|
||||||
|
const store = createStore(reducer, preloadedState, applyMiddleware(logger))
|
||||||
window.store = createStore(reducer, preloadedState, applyMiddleware(logger))
|
|
||||||
|
|
||||||
|
|
||||||
const App = ({ store }) => (
|
const App = ({ store }) => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<Tree />
|
<Tree />
|
||||||
|
<ToolTip/>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
ReactDOM.render(<App store={store} />, document.getElementById('react'));
|
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'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
@ -58,15 +58,12 @@ export const NavBar = () => {
|
||||||
|
|
||||||
const [_, forceUpdate] = useReducer(x => x + 1, 0);
|
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) => (
|
btnz.map(([Icon, fcn, txt], idx) => (
|
||||||
<div className="btn flex items-center justify-start p-1 text-lg" key={idx}
|
<Icon className="btn w-auto h-full p-2" tooltip={txt}
|
||||||
onClick={fcn}
|
onClick={fcn} key={idx}
|
||||||
>
|
/>
|
||||||
<Icon className="w-6 h-6" />
|
|
||||||
<div className="ml-2" tooltip="wtf">{txt}</div>
|
|
||||||
</div>
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
|
@ -11,6 +11,9 @@ export const preloadedState = {
|
||||||
tree: {},
|
tree: {},
|
||||||
order: {},
|
order: {},
|
||||||
},
|
},
|
||||||
|
ui: {
|
||||||
|
toolTipImmediate: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reducer(state = {}, action) {
|
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 = {
|
module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
app: './src/app.jsx',
|
app: './src/react/app.jsx',
|
||||||
scene: './src/Scene.js',
|
scene: './src/Scene.js',
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
|
Loading…
Reference in New Issue