diff --git a/src/lib/actions/clickOutside.ts b/src/lib/actions/clickOutside.ts new file mode 100644 index 0000000..357ced1 --- /dev/null +++ b/src/lib/actions/clickOutside.ts @@ -0,0 +1,28 @@ +export type ActionClickOutsideOptions = { + onClickOutside: () => void; + whitelist?: HTMLElement[]; +}; + +export default (node: HTMLElement, options: ActionClickOutsideOptions) => { + let whitelist = options.whitelist?.filter((el) => el !== undefined) ?? []; + let onOutsideClick = options.onClickOutside; + const detectClick = (event: MouseEvent) => { + const target = event.target as Node; + if (!node.contains(target) && !whitelist.some((el) => el.isSameNode(target))) { + onOutsideClick(); + } + return; + }; + const listenerOptions = { passive: true, capture: true }; + document.addEventListener('click', detectClick, listenerOptions); + + return { + destroy() { + document.removeEventListener('click', detectClick, listenerOptions); + }, + update(params: ActionClickOutsideOptions) { + whitelist = params.whitelist?.filter((el) => el !== undefined) ?? []; + onOutsideClick = params.onClickOutside; + } + }; +}; diff --git a/src/lib/actions/portal.ts b/src/lib/actions/portal.ts new file mode 100644 index 0000000..041c4cb --- /dev/null +++ b/src/lib/actions/portal.ts @@ -0,0 +1,73 @@ +export const portal = (node: HTMLElement) => { + const target = document.querySelector('main'); + target?.appendChild(node).focus(); + + return { + destroy() { + if (target?.contains(node)) { + target?.removeChild(node); + } + } + }; +}; + +const findParentWithClass = (element: HTMLElement, className: string): HTMLElement | null => { + let el: HTMLElement | null = element; + while (el) { + if (el.classList && el.classList.contains(className)) { + return el; + } + el = el.parentElement; + } + + return null; +}; + +export const defaultPortalRootClass = 'portal-root'; + +export const relativePortal = (node: HTMLElement, rootClass = defaultPortalRootClass) => { + // position node relative to its parent + const parent = node.parentElement; + if (!parent) { + return portal(node); + } + + const componentBounds = node.getBoundingClientRect(); + const parentBounds = parent.getBoundingClientRect(); + + let left = parentBounds.left; + let top = parentBounds.top + parentBounds.height + 3; + + if (window.innerWidth < left + componentBounds.width) { + left = window.innerWidth - (componentBounds.width + 25); + } + + if (window.innerHeight < top + componentBounds.height) { + top = window.innerHeight - (componentBounds.height + 25); + } + + node.style.left = `${left}px`; + node.style.top = `${top}px`; + + // TODO: Update position on resize + + // Find next portal-root class along the node tree to attack + const portalRoot = findParentWithClass(node, rootClass); + + let targetElement: HTMLElement | null; + if (portalRoot) { + targetElement = portalRoot; + } else { + targetElement = document.querySelector('main'); + } + + targetElement?.appendChild(node).focus(); + + return { + destroy() { + if (targetElement?.contains(node)) { + targetElement?.removeChild(node); + } + } + }; +}; diff --git a/src/lib/components/BasicGraph.svelte b/src/lib/components/BasicGraph.svelte index 6584aee..34f5a7a 100644 --- a/src/lib/components/BasicGraph.svelte +++ b/src/lib/components/BasicGraph.svelte @@ -18,6 +18,7 @@ import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'; import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass'; import Stats from './graph/Stats.svelte'; + import Graph2D from './graph/Graph2D.svelte'; export let onHover: (position: THREE.Vector2, object?: THREE.Object3D) => void = () => {}; @@ -145,30 +146,18 @@ const animate = (time: number) => { // call all before subscribers for (const subscriber of beforeSubscribers) { - subscriber(); + subscriber(renderer, scene, camera); } + // Update tween for all animations TWEEN.update(time); + controls.update(); - - // if (mousePosition) { - // // Handle selection - // raycaster.setFromCamera(mousePosition, camera); - - // // const intersections = dataRenderer.getIntersections(raycaster); - - // // if (intersections.length === 0) { - // // outlinePass.selectedObjects = []; - // // onHover(mouseClientPosition, undefined); - // // } else { - // // outlinePass.selectedObjects = [intersections[0].object]; - // // onHover(mouseClientPosition, intersections[0].object); - // // } - // } - composer.render(); + for (const subscriber of afterSubscribers) { - subscriber(); + subscriber(renderer, scene, camera); } + requestAnimationFrame(animate); }; @@ -256,18 +245,18 @@ } -