From a7c211a1059db4e6890b247c3c6c91114761c0f0 Mon Sep 17 00:00:00 2001 From: howard Date: Wed, 7 Apr 2021 15:50:53 -0700 Subject: [PATCH] implement tooltip solution --- dist/index.html | 3 +- src/Scene.js | 15 +++-- src/Sketch.js | 17 ++++-- src/mouseEvents.js | 7 ++- src/{ => react}/app.css | 33 +++++++++- src/{ => react}/app.jsx | 13 ++-- src/{ => react}/depTree.mjs | 0 src/{ => react}/icons.jsx | 0 src/{ => react}/navBar.jsx | 13 ++-- src/{ => react}/reducer.js | 3 + src/react/toolTip.jsx | 86 +++++++++++++++++++++++++++ src/{treeEntry.jsx => react/tree.jsx} | 0 webpack.common.js | 2 +- 13 files changed, 158 insertions(+), 34 deletions(-) rename src/{ => react}/app.css (59%) rename src/{ => react}/app.jsx (75%) rename src/{ => react}/depTree.mjs (100%) rename src/{ => react}/icons.jsx (100%) rename src/{ => react}/navBar.jsx (89%) rename src/{ => react}/reducer.js (98%) create mode 100644 src/react/toolTip.jsx rename src/{treeEntry.jsx => react/tree.jsx} (100%) diff --git a/dist/index.html b/dist/index.html index dc20e8a..1ed5ab3 100644 --- a/dist/index.html +++ b/dist/index.html @@ -12,7 +12,8 @@ - + + CAD Tool diff --git a/src/Scene.js b/src/Scene.js index 28d6c83..95ff9d1 100644 --- a/src/Scene.js +++ b/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( diff --git a/src/Sketch.js b/src/Sketch.js index da4d2ad..bc5e70a 100644 --- a/src/Sketch.js +++ b/src/Sketch.js @@ -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 diff --git a/src/mouseEvents.js b/src/mouseEvents.js index d5eabe6..9e2bde2 100644 --- a/src/mouseEvents.js +++ b/src/mouseEvents.js @@ -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)) { diff --git a/src/app.css b/src/react/app.css similarity index 59% rename from src/app.css rename to src/react/app.css index 9c93aae..7ad9bff 100644 --- a/src/app.css +++ b/src/react/app.css @@ -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; +} \ No newline at end of file diff --git a/src/app.jsx b/src/react/app.jsx similarity index 75% rename from src/app.jsx rename to src/react/app.jsx index 0014030..b07d366 100644 --- a/src/app.jsx +++ b/src/react/app.jsx @@ -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 }) => ( + ); - document.addEventListener('DOMContentLoaded', () => { ReactDOM.render(, document.getElementById('react')); }); +window.store = store \ No newline at end of file diff --git a/src/depTree.mjs b/src/react/depTree.mjs similarity index 100% rename from src/depTree.mjs rename to src/react/depTree.mjs diff --git a/src/icons.jsx b/src/react/icons.jsx similarity index 100% rename from src/icons.jsx rename to src/react/icons.jsx diff --git a/src/navBar.jsx b/src/react/navBar.jsx similarity index 89% rename from src/navBar.jsx rename to src/react/navBar.jsx index da17d4c..ef79ae7 100644 --- a/src/navBar.jsx +++ b/src/react/navBar.jsx @@ -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
+ return
{ btnz.map(([Icon, fcn, txt], idx) => ( -
- -
{txt}
-
+ )) }
diff --git a/src/reducer.js b/src/react/reducer.js similarity index 98% rename from src/reducer.js rename to src/react/reducer.js index f4a193a..20101e1 100644 --- a/src/reducer.js +++ b/src/react/reducer.js @@ -11,6 +11,9 @@ export const preloadedState = { tree: {}, order: {}, }, + ui: { + toolTipImmediate: false + } } export function reducer(state = {}, action) { diff --git a/src/react/toolTip.jsx b/src/react/toolTip.jsx new file mode 100644 index 0000000..e3ae4f5 --- /dev/null +++ b/src/react/toolTip.jsx @@ -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
+ {state} +
+
+ +} + + +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; +} diff --git a/src/treeEntry.jsx b/src/react/tree.jsx similarity index 100% rename from src/treeEntry.jsx rename to src/react/tree.jsx diff --git a/webpack.common.js b/webpack.common.js index 9d42bb3..2743dfe 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -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: {