diff --git a/app/DJTooltip.tsx b/app/DJTooltip.tsx index f0df0ec..4500d2e 100644 --- a/app/DJTooltip.tsx +++ b/app/DJTooltip.tsx @@ -1,32 +1,78 @@ -import { watchEffect, watch, onMounted, type CSSProperties, defineComponent, ref } from "vue"; +import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue"; +import { h as djh } from "@/util.ts"; -const carrierStyle = { - opacity: "0", - display: "block", - pointerEvents: "none", - backgroundColor: "black", - border: "white solid 1px", - color: "white", - padding: "10px", - position: "absolute", - zIndex: "1", - overflow: "hidden", - height: "0", - width: "0", - transition: "opacity 200ms, height 200ms, width 200ms", -} satisfies CSSProperties; +type TooltipContext = { + show: (newText: string, x: number, y: number) => void, + hide: () => void, +}; -const textCarrierStyle = { - fontSize: '16px', - fontFamily: "Roboto, serif", - display: "block", - overflow: "hidden", -} satisfies CSSProperties; +const tooltipContext = Symbol('tooltip') as InjectionKey; -const defaultWidth = 350; +export function setupTooltip(options: { carrier: Ref }) { + const { carrier } = options; + + watchEffect(() => { + if (carrier.value) { + carrier.value.classList.add('tooltip-carrier'); + carrier.value.appendChild(djh('div', { className: 'text-carrier' })); + } + }); + + const listener = (pos: { x: number, y: number }) => { + if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") { + if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) { + carrier.value.style.left = (pos.x + 15) + "px"; + } else { + carrier.value.style.left = (document.body.scrollWidth - carrier.value.clientWidth - 5) + "px"; + } + if (pos.y + carrier.value.clientHeight <= document.body.scrollHeight) { + carrier.value.style.top = pos.y + "px"; + } else { + carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px"; + } + } + }; + + const active = ref(false); + + watch(active, async () => { + const tooltipCarrier = carrier.value; + if (tooltipCarrier) { + tooltipCarrier.style.opacity = active.value ? '1' : '0'; + tooltipCarrier.style.width = active.value ? '350px' : '0'; + await nextTick(); + if (active.value) { + if (tooltipCarrier.firstChild?.nodeType === Node.ELEMENT_NODE) { + const computedHeight = getComputedStyle(tooltipCarrier.firstChild as Element).height; + tooltipCarrier.style.height = computedHeight; + } + } else { + tooltipCarrier.style.height = '0'; + } + } + }); + + onMounted(() => document.addEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY }))); + onBeforeUnmount(() => document.removeEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY }))); + + const ctx: TooltipContext = { + show(tooltip, x, y) { + if (carrier.value) { + carrier.value.firstChild!.textContent = tooltip; + } + active.value = true; + listener({ x, y }); + }, + hide() { + active.value = false; + }, + }; + + provide(tooltipContext, ctx); +} export default defineComponent({ - name: "dj-sexy-tooltip", + name: "dj-tooltip", props: { tooltip: { type: String, @@ -34,47 +80,15 @@ export default defineComponent({ }, }, setup(props, { slots, attrs }) { - const active = ref(false); - - const carrier = ref(null); - const textCarrier = ref(null); - - onMounted(() => { - document.addEventListener("mousemove", (event) => { - const pos = { x: event.pageX, y: event.pageY }; - if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") { - if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) { - carrier.value.style.left = (pos.x + 15) + "px"; - } else { - carrier.value.style.left = (document.body.scrollWidth - carrier.value.clientWidth - 5) + "px"; - } - if (pos.y + carrier.value.clientHeight <= document.body.scrollHeight) { - carrier.value.style.top = pos.y + "px"; - } else { - carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px"; - } - } - }); - }); - - watchEffect(() => { - if (carrier.value) { - carrier.value.style.height = active.value ? '16px' : '0'; - carrier.value.style.opacity = active.value ? '1' : '0'; - carrier.value.style.width = active.value ? '350px' : '0'; - } - }); + const tooltip = inject(tooltipContext, () => { throw new Error('No tooltip context'); }, true); return () => <> -
{ active.value = true; }} - onMouseleave={() => { active.value = false; }} - > +
tooltip.show(props.tooltip, e.pageX, e.pageY)} + onMouseleave={() => tooltip.hide()}> {slots.default && }
-
- {props.tooltip} -
; }, }); diff --git a/app/home/App.tsx b/app/home/DJHomeRoot.tsx similarity index 74% rename from app/home/App.tsx rename to app/home/DJHomeRoot.tsx index 59e0476..b9bcd2c 100644 --- a/app/home/App.tsx +++ b/app/home/DJHomeRoot.tsx @@ -1,24 +1,50 @@ -import { defineComponent } from "vue"; +import { defineComponent, computed, ref, type Ref } from "vue"; import useHead from "@/useHead.ts"; -import DJTooltip from "@/DJTooltip.tsx"; +import DJTooltip, { setupTooltip } from "@/DJTooltip.tsx"; import DJEmail from "@/DJEmail.tsx"; export default defineComponent({ name: "app-root", setup() { useHead({ title: "DJ Ledda's Homepage" }); - return () => ( + + const tooltipCarrier = ref(null); + setupTooltip({ carrier: tooltipCarrier }); + + const dude1Spinning = ref(false); + const dude2Spinning = ref(false); + + function toggleDude(event: MouseEvent, dudeRef: Ref) { + const dude = event.target as HTMLImageElement; + if (dudeRef.value) { + dude.addEventListener("animationiteration", function listener() { + dudeRef.value = false; + dude.removeEventListener("animationiteration", listener as EventListenerOrEventListenerObject); + }); + } else { + dudeRef.value = true; + } + } + + const shaking = computed(() => dude1Spinning.value || dude2Spinning.value); + + return () => <> +
-
+
- dj legt krasse Mucke auf + dj legt krasse Mucke auf toggleDude(e, dude1Spinning)} /> DJ Ledda - dj laying down some sick beats + dj laying down some sick beats toggleDude(e, dude2Spinning) } />
@@ -71,6 +97,6 @@ export default defineComponent({
- ); + ; }, }); diff --git a/app/home/client.ts b/app/home/client.ts index f5df9e9..aea082e 100644 --- a/app/home/client.ts +++ b/app/home/client.ts @@ -1,4 +1,4 @@ import { createSSRApp } from "vue"; -import App from "@/home/App.tsx"; +import DJHomeRoot from "@/home/DJHomeRoot.tsx"; -createSSRApp(App).mount("#app-root"); +createSSRApp(DJHomeRoot).mount("#app-root"); diff --git a/app/home/server.ts b/app/home/server.ts index 6809992..e218e76 100644 --- a/app/home/server.ts +++ b/app/home/server.ts @@ -1,7 +1,7 @@ import { createSSRApp } from "vue"; -import App from "@/home/App.tsx"; +import DJHomeRoot from "@/home/DJHomeRoot.tsx"; export default function createApp() { - const app = createSSRApp(App); + const app = createSSRApp(DJHomeRoot); return { app, router: null }; } diff --git a/app/util.ts b/app/util.ts index 6bb176a..199d2fb 100644 --- a/app/util.ts +++ b/app/util.ts @@ -42,6 +42,7 @@ export function css(strs: TemplateStringsArray, ...vals: string[]) { return sheet; } +/* export class DJElement extends HTMLElement { static styles: CSSStyleSheet; @@ -57,3 +58,4 @@ export class DJElement extends HTMLElement { this.root.adoptedStyleSheets = statics.styles ? [statics.styles] : []; } } +*/ diff --git a/public/home/js/SexyTooltip.ts b/public/home/js/SexyTooltip.ts deleted file mode 100644 index 76002cd..0000000 --- a/public/home/js/SexyTooltip.ts +++ /dev/null @@ -1,130 +0,0 @@ -interface Point { - x: number; - y: number; -} - -interface Size { - height: string; - width: string; -} - -class SexyTooltip { - private static readonly carrierStyle: Partial = { - opacity: "0", - display: "block", - pointerEvents: "none", - backgroundColor: "black", - border: "white solid 1px", - color: "white", - padding: "10px", - position: "absolute", - zIndex: "1", - overflow: "hidden", - height: "0", - width: "0", - transition: "opacity 200ms, height 200ms, width 200ms", - }; - private static readonly textCarrierStyle: Partial = { - width: "350px", - display: "block", - overflow: "hidden", - }; - private static readonly defaultWidth = 350; - private readonly carrier: HTMLDivElement; - private readonly textCarrier: HTMLSpanElement; - private readonly elementsWithTooltips: Element[] = []; - private active: boolean = false; - - constructor(carrier: HTMLDivElement) { - if (carrier.childNodes.length > 0) { - throw new Error("Incorrect setup for tooltip! Remove the child nodes from the tooltip div."); - } - this.carrier = carrier; - this.textCarrier = document.createElement("span"); - this.carrier.appendChild(this.textCarrier); - Object.assign(this.carrier.style, SexyTooltip.carrierStyle); - Object.assign(this.textCarrier.style, SexyTooltip.textCarrierStyle); - document.querySelectorAll(".tooltip").forEach((element) => { - if (element.nodeName === "SPAN") { - console.log(element.previousElementSibling); - this.elementsWithTooltips.push(element.previousElementSibling); - } - }); - document.addEventListener("mousemove", (event) => this.rerenderTooltip({ x: event.pageX, y: event.pageY })); - } - - rerenderTooltip(mousePos: Point) { - const newText = this.getTooltipTextForHoveredElement(mousePos); - if (newText !== this.textCarrier.innerHTML) { - this.transitionTooltipSize(newText); - const tooltipHasText = newText !== ""; - if (tooltipHasText !== this.active) { - this.toggleActive(); - } - } - if (this.isStillVisible()) { - this.updatePosition(mousePos); - } - } - - isStillVisible() { - return getComputedStyle(this.carrier).opacity !== "0"; - } - - updatePosition(pos: Point) { - if (pos.x + 15 + this.carrier.clientWidth <= document.body.scrollWidth) { - this.carrier.style.left = (pos.x + 15) + "px"; - } else { - this.carrier.style.left = (document.body.scrollWidth - this.carrier.clientWidth - 5) + "px"; - } - if (pos.y + this.carrier.clientHeight <= document.body.scrollHeight) { - this.carrier.style.top = pos.y + "px"; - } else { - this.carrier.style.top = (document.body.scrollHeight - this.carrier.clientHeight - 5) + "px"; - } - } - - toggleActive() { - if (this.active) { - this.active = false; - this.carrier.style.opacity = "0"; - this.carrier.style.padding = "0"; - } else { - this.active = true; - this.carrier.style.opacity = "1"; - this.carrier.style.padding = SexyTooltip.carrierStyle.padding; - } - } - - transitionTooltipSize(text: string) { - const testDiv = document.createElement("div"); - testDiv.style.height = "auto"; - testDiv.style.width = SexyTooltip.defaultWidth + "px"; - testDiv.innerHTML = text; - document.body.appendChild(testDiv); - const calculatedHeight = testDiv.scrollHeight; - testDiv.style.display = "inline"; - testDiv.style.width = "auto"; - document.body.removeChild(testDiv); - const size = { height: calculatedHeight + "px", width: "350px" }; - this.carrier.style.height = size.height; - this.carrier.style.width = text === "" ? "0" : size.width; - this.textCarrier.innerHTML = text; - } - - getTooltipTextForHoveredElement(mousePos: Point): string { - for (const elem of this.elementsWithTooltips) { - const boundingRect = elem.getBoundingClientRect(); - const inYRange = mousePos.y >= boundingRect.top + window.pageYOffset - 1 && - mousePos.y <= boundingRect.bottom + window.pageYOffset + 1; - const inXRange = mousePos.x >= boundingRect.left + window.pageXOffset - 1 && - mousePos.x <= boundingRect.right + window.pageXOffset + 1; - if (inYRange && inXRange) { - return (elem.nextElementSibling as HTMLSpanElement)?.innerText ?? ""; - } - } - return ""; - } -} - -export default SexyTooltip; diff --git a/public/home/main.css b/public/home/main.css index 80d095d..b4a1547 100644 --- a/public/home/main.css +++ b/public/home/main.css @@ -166,7 +166,7 @@ span.subjecttitle { padding: 10px 20px 10px 20px; display: block; color: #333333; - text-decoration: none; + text-decorAtion: none; transition: background-color 200ms; } @@ -187,3 +187,31 @@ a { .tooltip-container { display: inline-block; } + +.tooltip-carrier { + opacity: 0; + display: block; + pointer-events: none; + background-color: black; + border: white solid 1px; + color: white; + padding: 10px; + position: absolute; + z-index: 1; + overflow: hidden; + height: 0; + width: 0; + position: absolute; + transition: opacity 200ms, height 200ms, width 200ms; + + .text-carrier { + position: absolute; + width: 350px; + font-size: 16px; + font-family: "Roboto", serif; + display: block; + overflow: hidden; + } +} + +