浏览代码

feat[krpano]: hotspot 支持 html

chenlei 1 年之前
父节点
当前提交
3fdf725fe7

+ 3 - 3
packages/krpano/src/components/Autorotate.tsx

@@ -1,4 +1,4 @@
-import React, { useContext, useEffect } from "react";
+import React, { memo, useContext, useEffect } from "react";
 import { KrpanoRendererContext } from "../contexts";
 
 /**
@@ -40,7 +40,7 @@ export interface AutorotateProps {
   interruptionevents?: string;
 }
 
-export const Autorotate: React.FC<AutorotateProps> = (props) => {
+export const Autorotate: React.FC<AutorotateProps> = memo((props) => {
   const renderer = useContext(KrpanoRendererContext);
 
   useEffect(() => {
@@ -53,4 +53,4 @@ export const Autorotate: React.FC<AutorotateProps> = (props) => {
   }, [renderer, props]);
 
   return <div className="autorotate" />;
-};
+});

+ 3 - 3
packages/krpano/src/components/Events.tsx

@@ -1,4 +1,4 @@
-import { FC, useContext, useEffect } from "react";
+import { FC, memo, useContext, useEffect } from "react";
 import { KrpanoRendererContext } from "../contexts/KrpanoRendererContext";
 import { EventCallback } from "../types";
 import { mapEventPropsToJSCall } from "../utils";
@@ -57,7 +57,7 @@ export interface EventsProps extends EventsConfig {}
 
 const GlobalEvents = "__GlobalEvents";
 
-export const Events: FC<EventsProps> = ({ name, ...EventsAttrs }) => {
+export const Events: FC<EventsProps> = memo(({ name, ...EventsAttrs }) => {
   const renderer = useContext(KrpanoRendererContext);
   const EventSelector = `events[${name || GlobalEvents}]`;
 
@@ -85,4 +85,4 @@ export const Events: FC<EventsProps> = ({ name, ...EventsAttrs }) => {
   }, [name, renderer]);
 
   return <div className="events"></div>;
-};
+});

+ 21 - 9
packages/krpano/src/components/HotSpot.tsx

@@ -1,13 +1,21 @@
-import { FC, ReactNode, useContext, useEffect, useMemo } from "react";
+import { FC, ReactNode, memo, useContext, useEffect, useMemo } from "react";
 import { EventCallback } from "../types";
 import { KrpanoRendererContext } from "../contexts";
-import { buildKrpanoAction, mapEventPropsToJSCall, mapObject } from "../utils";
+import {
+  buildKrpanoAction,
+  childrenToOuterHTML,
+  mapEventPropsToJSCall,
+  mapObject,
+} from "../utils";
 
 export interface HotspotProps {
   name: string;
   children?: ReactNode;
   url?: string;
-  type?: string;
+  /**
+   * @see https://krpano.com/docu/xml/?version=121#layer.type
+   */
+  type?: "image" | "text";
   keep?: boolean;
   visible?: boolean;
   enabled?: boolean;
@@ -33,6 +41,7 @@ export interface HotspotProps {
   scale?: number;
   rotate?: number;
   alpha?: number;
+  bg?: boolean;
   onOver?: EventCallback;
   onHover?: EventCallback;
   onOut?: EventCallback;
@@ -42,14 +51,15 @@ export interface HotspotProps {
   onLoaded?: EventCallback;
 }
 
-export const HotSpot: FC<HotspotProps> = ({ name, children, ...rest }) => {
+export const HotSpot: FC<HotspotProps> = memo(({ name, ...rest }) => {
   const EventSelector = `hotspot[${name}]`;
   const renderer = useContext(KrpanoRendererContext);
   const options = useMemo(() => {
-    const { scale = 0.5, ...r } = rest;
+    const { scale = 0.5, children, ...r } = rest;
 
     return {
       scale,
+      html: r.type === "text" ? childrenToOuterHTML(children) : null,
       onOver: buildKrpanoAction("tween", "scale", scale + 0.05),
       onOut: buildKrpanoAction("tween", "scale", scale),
       ...r,
@@ -76,18 +86,20 @@ export const HotSpot: FC<HotspotProps> = ({ name, children, ...rest }) => {
   }, []);
 
   useEffect(() => {
-    renderer?.setTag(
+    if (!renderer) return;
+
+    renderer.setTag(
       "hotspot",
       name,
       Object.assign(
         { ...options },
         mapEventPropsToJSCall(
           { ...options },
-          (key) => `js(${renderer?.name}.fire(${key},${EventSelector}))`
+          (key) => `js(${renderer.name}.fire(${key},${EventSelector}))`
         )
       )
     );
   }, [renderer, name, options]);
 
-  return <div className="hotspot">{children}</div>;
-};
+  return <div className="hotspot" />;
+});

+ 3 - 5
packages/krpano/src/components/View.tsx

@@ -1,4 +1,4 @@
-import React, { useContext, useEffect } from "react";
+import React, { memo, useContext, useEffect } from "react";
 import { KrpanoRendererContext } from "../contexts";
 
 /**
@@ -42,7 +42,7 @@ export interface ViewProps {
   children?: null;
 }
 
-export const View: React.FC<ViewProps> = ({ children, ...viewAttrs }) => {
+export const View: React.FC<ViewProps> = memo(({ children, ...viewAttrs }) => {
   const renderer = useContext(KrpanoRendererContext);
 
   useEffect(() => {
@@ -50,6 +50,4 @@ export const View: React.FC<ViewProps> = ({ children, ...viewAttrs }) => {
   }, [renderer, viewAttrs]);
 
   return <div className="view">{children}</div>;
-};
-
-export default View;
+});

+ 13 - 3
packages/krpano/src/utils.ts

@@ -1,5 +1,7 @@
+import { ReactNode } from "react";
 import escapeHTML from "escape-html";
 import { XMLMeta } from "./types";
+import ReactDOMServer from "react-dom/server";
 
 /**
  * @see https://krpano.com/docu/actions/?version=119pr13
@@ -42,8 +44,7 @@ export const buildKrpanoTagSetterActions = (
       }
       // 如果属性值中有双引号,需要改用单引号
       let quote = '"';
-      if (val.toString().includes(quote)) {
-        // eslint-disable-next-line quotes
+      if (`${val}`.includes(quote)) {
         quote = "'";
       }
       if (key === "style") {
@@ -54,7 +55,7 @@ export const buildKrpanoTagSetterActions = (
       }
       // content是XML文本,不能转义,因为不涉及用户输入也不需要
       return `set(${name}.${key}, ${quote}${
-        key === "content" ? val : escapeHTML(val.toString())
+        ["content", "html"].includes(key) ? val : escapeHTML(val.toString())
       }${quote});`;
     })
     .filter((str) => !!str)
@@ -105,3 +106,12 @@ export const mapEventPropsToJSCall = (
     }
     return {};
   }) as Record<string, string>;
+
+export const childrenToOuterHTML = (children: ReactNode) => {
+  const wrapper = document.createElement("div");
+  const childrenString = ReactDOMServer.renderToStaticMarkup(<>{children}</>);
+
+  wrapper.innerHTML = childrenString;
+
+  return wrapper.outerHTML;
+};