From 333610776ffdf762b7335bc7c0461d7a067eabd9 Mon Sep 17 00:00:00 2001 From: keiya sasaki Date: Wed, 15 Feb 2023 15:36:04 +0900 Subject: [PATCH] feat: use visible field on reearth/core (#456) --- .../Widgets/Widget/Button/MenuButton.tsx | 2 ++ .../Crust/Widgets/Widget/Button/index.tsx | 19 ++++++++++-- .../Crust/Widgets/Widget/Navigator/hooks.ts | 16 +++++++++- .../Crust/Widgets/Widget/Navigator/index.tsx | 19 ++++++++++-- .../Widgets/Widget/SplashScreen/index.tsx | 12 ++++++- .../Crust/Widgets/Widget/Timeline/hooks.ts | 19 ++++++++++-- .../Crust/Widgets/Widget/Timeline/index.tsx | 19 +++++++++--- src/core/Crust/Widgets/Widget/index.tsx | 2 ++ src/core/Crust/Widgets/Widget/useVisible.ts | 31 +++++++++++++++++++ src/core/Crust/Widgets/index.tsx | 4 ++- 10 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 src/core/Crust/Widgets/Widget/useVisible.ts diff --git a/src/core/Crust/Widgets/Widget/Button/MenuButton.tsx b/src/core/Crust/Widgets/Widget/Button/MenuButton.tsx index 6e4817c568..ee7066ce27 100644 --- a/src/core/Crust/Widgets/Widget/Button/MenuButton.tsx +++ b/src/core/Crust/Widgets/Widget/Button/MenuButton.tsx @@ -7,6 +7,7 @@ import Text from "@reearth/components/atoms/Text"; import { styled, metricsSizes, mask } from "@reearth/theme"; import type { Camera, FlyToDestination, Theme } from "../types"; +import { Visible } from "../useVisible"; export type Button = { id: string; @@ -18,6 +19,7 @@ export type Button = { buttonColor?: string; buttonBgcolor?: string; buttonCamera?: Camera; + visible: Visible; }; export type MenuItem = { diff --git a/src/core/Crust/Widgets/Widget/Button/index.tsx b/src/core/Crust/Widgets/Widget/Button/index.tsx index d29fd9c622..a50750b130 100644 --- a/src/core/Crust/Widgets/Widget/Button/index.tsx +++ b/src/core/Crust/Widgets/Widget/Button/index.tsx @@ -1,6 +1,7 @@ import { styled } from "@reearth/theme"; import type { ComponentProps as WidgetProps } from ".."; +import { useVisible } from "../useVisible"; import MenuButton, { Button as ButtonType, MenuItem as MenuItemType } from "./MenuButton"; @@ -12,10 +13,22 @@ export type Property = { menu?: MenuItem[]; }; -const Menu = ({ widget, theme, context: { onFlyTo } = {} }: Props): JSX.Element | null => { +const Menu = ({ + widget, + theme, + isMobile, + onVisibilityChange, + context: { onFlyTo } = {}, +}: Props): JSX.Element | null => { const { default: button, menu: menuItems } = widget.property ?? {}; + const visible = useVisible({ + widgetId: widget.id, + visible: widget.property?.default?.visible, + isMobile, + onVisibilityChange, + }); - return ( + return visible ? ( - ); + ) : null; }; const Wrapper = styled.div` diff --git a/src/core/Crust/Widgets/Widget/Navigator/hooks.ts b/src/core/Crust/Widgets/Widget/Navigator/hooks.ts index 0d4ca36c4a..423c426247 100644 --- a/src/core/Crust/Widgets/Widget/Navigator/hooks.ts +++ b/src/core/Crust/Widgets/Widget/Navigator/hooks.ts @@ -1,30 +1,43 @@ import { useState, useEffect, useCallback, useRef } from "react"; -import type { Camera, FlyToDestination } from "../types"; +import type { Camera, FlyToDestination, Widget } from "../types"; +import { useVisible } from "../useVisible"; import { degreeToRadian, radianToDegree } from "./NavigatorPresenter"; export default function ({ camera, initialCamera, + widget, + isMobile, onZoomIn, onZoomOut, onCameraOrbit, onCameraRotateRight, onFlyTo, + onVisibilityChange, }: { camera?: Camera; initialCamera?: Camera; + widget: Widget; + isMobile?: boolean; onZoomIn?: (amount: number) => void; onZoomOut?: (amount: number) => void; onCameraOrbit?: (orbit: number) => void; onCameraRotateRight?: (radian: number) => void; onFlyTo?: (target: string | FlyToDestination, options?: { duration?: number }) => void; + onVisibilityChange?: (id: string, visible: boolean) => void; }) { const [degree, setDegree] = useState(0); const [isHelpOpened, setIsHelpOpened] = useState(false); const orbitRadianRef = useRef(0); const isMovingOrbit = useRef(false); + const visible = useVisible({ + widgetId: widget.id, + visible: widget.property.default.visible, + isMobile, + onVisibilityChange, + }); const handleOnRotate = useCallback( (deg: number) => { @@ -88,6 +101,7 @@ export default function ({ return { degree, isHelpOpened, + visible, events: { onRotate: handleOnRotate, onStartOrbit: handleOnStartOrbit, diff --git a/src/core/Crust/Widgets/Widget/Navigator/index.tsx b/src/core/Crust/Widgets/Widget/Navigator/index.tsx index 7434b14864..6807f2724d 100644 --- a/src/core/Crust/Widgets/Widget/Navigator/index.tsx +++ b/src/core/Crust/Widgets/Widget/Navigator/index.tsx @@ -1,15 +1,23 @@ import type { ComponentProps as WidgetProps } from ".."; +import { Visible } from "../useVisible"; import useHooks from "./hooks"; import NavigatorPresenter from "./NavigatorPresenter"; export type Props = WidgetProps; -export type Property = {}; +export type Property = { + default: { + visible: Visible; + }; +}; const Navigator = ({ theme, editing, + widget, + isMobile, + onVisibilityChange, context: { camera, initialCamera, @@ -20,17 +28,22 @@ const Navigator = ({ onZoomOut, } = {}, }: Props): JSX.Element | null => { - const { degree, events } = useHooks({ + const { degree, visible, events } = useHooks({ camera, initialCamera, + widget, + isMobile, onCameraOrbit, onCameraRotateRight, onFlyTo, onZoomIn, onZoomOut, + onVisibilityChange, }); - return ; + return visible ? ( + + ) : null; }; export default Navigator; diff --git a/src/core/Crust/Widgets/Widget/SplashScreen/index.tsx b/src/core/Crust/Widgets/Widget/SplashScreen/index.tsx index a8f6cc26c6..e4ada43905 100644 --- a/src/core/Crust/Widgets/Widget/SplashScreen/index.tsx +++ b/src/core/Crust/Widgets/Widget/SplashScreen/index.tsx @@ -6,6 +6,7 @@ import { styled } from "@reearth/theme"; import type { ComponentProps as WidgetProps } from ".."; import type { Camera } from "../types"; +import { useVisible, Visible } from "../useVisible"; export type Props = WidgetProps; @@ -20,6 +21,7 @@ export type Property = { overlayImageW?: number; overlayImageH?: number; overlayTitle?: string; + visible?: Visible; }; camera?: { cameraPosition?: Camera; @@ -31,6 +33,8 @@ export type Property = { const SplashScreen = ({ widget, isBuilt, + isMobile, + onVisibilityChange, context: { onFlyTo } = {}, }: Props): JSX.Element | null => { const { property } = widget ?? {}; @@ -46,6 +50,12 @@ const SplashScreen = ({ overlayTitle: title, } = property?.overlay ?? {}; const camera = property?.camera?.filter(c => !!c.cameraPosition); + const visible = useVisible({ + widgetId: widget.id, + visible: widget.property?.overlay.visible, + isMobile, + onVisibilityChange, + }); const [cameraSequence, setCameraSequence] = useState(0); const [delayedCameraSequence, setDelayedCameraSequence] = useState(-1); @@ -96,7 +106,7 @@ const SplashScreen = ({ return () => clearTimeout(t); }, [delayedCurrentCamera, isBuilt]); - return state === "unmounted" ? null : ( + return !visible || state === "unmounted" ? null : ( {title} diff --git a/src/core/Crust/Widgets/Widget/Timeline/hooks.ts b/src/core/Crust/Widgets/Widget/Timeline/hooks.ts index e4edda9322..53a4c4423d 100644 --- a/src/core/Crust/Widgets/Widget/Timeline/hooks.ts +++ b/src/core/Crust/Widgets/Widget/Timeline/hooks.ts @@ -3,7 +3,8 @@ import { useState, useCallback, useEffect, useRef } from "react"; import type { TimeEventHandler } from "@reearth/components/atoms/Timeline"; import { TickEvent, TickEventCallback } from "@reearth/core/Map"; -import type { Clock } from "../types"; +import type { Clock, Widget } from "../types"; +import { useVisible } from "../useVisible"; const getOrNewDate = (d?: Date) => d ?? new Date(); const makeRange = (startTime?: number, stopTime?: number) => { @@ -16,8 +17,9 @@ const makeRange = (startTime?: number, stopTime?: number) => { const DEFAULT_SPEED = 1; export const useTimeline = ({ - widgetId, + widget, clock, + isMobile, onPlay, onPause, onTimeChange, @@ -25,9 +27,11 @@ export const useTimeline = ({ onTick, removeTickEventListener, onExtend, + onVisibilityChange, }: { - widgetId: string; + widget: Widget; clock?: Clock; + isMobile?: boolean; onPlay?: () => void; onPause?: () => void; onSpeedChange?: (speed: number) => void; @@ -35,7 +39,15 @@ export const useTimeline = ({ onTick?: TickEvent; removeTickEventListener?: TickEvent; onExtend?: (id: string, extended: boolean | undefined) => void; + onVisibilityChange?: (id: string, v: boolean) => void; }) => { + const visible = useVisible({ + widgetId: widget.id, + visible: widget.property.default.visible, + isMobile, + onVisibilityChange, + }); + const widgetId = widget.id; const [range, setRange] = useState(() => makeRange(clock?.start?.getTime(), clock?.stop?.getTime()), ); @@ -154,6 +166,7 @@ export const useTimeline = ({ range, isOpened, currentTime, + visible, events: { onOpen: handleOnOpen, onClose: handleOnClose, diff --git a/src/core/Crust/Widgets/Widget/Timeline/index.tsx b/src/core/Crust/Widgets/Widget/Timeline/index.tsx index 03b3039573..ea48e26bdd 100644 --- a/src/core/Crust/Widgets/Widget/Timeline/index.tsx +++ b/src/core/Crust/Widgets/Widget/Timeline/index.tsx @@ -2,17 +2,24 @@ import TimelineUI from "@reearth/components/atoms/Timeline"; import { styled } from "@reearth/theme"; import type { ComponentProps as WidgetProps } from ".."; +import { Visible } from "../useVisible"; import { useTimeline } from "./hooks"; export type Props = WidgetProps; -export type Property = {}; +export type Property = { + default: { + visible: Visible; + }; +}; const Timeline = ({ widget, theme, + isMobile, onExtend, + onVisibilityChange, context: { clock, onPlay, @@ -23,9 +30,10 @@ const Timeline = ({ removeTickEventListener, } = {}, }: Props): JSX.Element | null => { - const { isOpened, currentTime, range, speed, events } = useTimeline({ - widgetId: widget.id, + const { isOpened, currentTime, range, speed, events, visible } = useTimeline({ + widget, clock, + isMobile, onPlay, onPause, onSpeedChange, @@ -33,9 +41,10 @@ const Timeline = ({ onTick, removeTickEventListener, onExtend, + onVisibilityChange, }); - return ( + return visible ? ( - ); + ) : null; }; const Widget = styled.div<{ diff --git a/src/core/Crust/Widgets/Widget/index.tsx b/src/core/Crust/Widgets/Widget/index.tsx index 21a18a3a09..b20045be4a 100644 --- a/src/core/Crust/Widgets/Widget/index.tsx +++ b/src/core/Crust/Widgets/Widget/index.tsx @@ -26,8 +26,10 @@ export type Props = { theme?: Theme; isEditable?: boolean; isBuilt?: boolean; + isMobile?: boolean; context?: Context; onExtend?: (id: string, extended: boolean | undefined) => void; + onVisibilityChange?: (id: string, visible: boolean) => void; renderWidget?: (w: Widget) => ReactNode; }; diff --git a/src/core/Crust/Widgets/Widget/useVisible.ts b/src/core/Crust/Widgets/Widget/useVisible.ts new file mode 100644 index 0000000000..357826be35 --- /dev/null +++ b/src/core/Crust/Widgets/Widget/useVisible.ts @@ -0,0 +1,31 @@ +import { useEffect, useMemo } from "react"; + +export type Visible = "always" | "desktop" | "mobile"; + +export const useVisible = ({ + widgetId, + visible: defaultVisible, + isMobile, + onVisibilityChange, +}: { + widgetId: string | undefined; + visible: Visible | undefined; + isMobile: boolean | undefined; + onVisibilityChange: ((id: string, v: boolean) => void) | undefined; +}) => { + const visible = useMemo( + () => + defaultVisible === "always" || + (defaultVisible === "desktop" && !isMobile) || + (defaultVisible === "mobile" && !!isMobile), + [defaultVisible, isMobile], + ); + + useEffect(() => { + if (widgetId) { + onVisibilityChange?.(widgetId, visible); + } + }, [widgetId, visible, onVisibilityChange]); + + return visible; +}; diff --git a/src/core/Crust/Widgets/index.tsx b/src/core/Crust/Widgets/index.tsx index f25576a664..5b8d9a9a29 100644 --- a/src/core/Crust/Widgets/index.tsx +++ b/src/core/Crust/Widgets/index.tsx @@ -91,6 +91,7 @@ export default function Widgets({ theme={theme} isEditable={isEditable} isBuilt={isBuilt} + isMobile={isMobile} context={context} renderWidget={widget2 => renderWidget?.({ @@ -103,10 +104,11 @@ export default function Widgets({ onVisibilityChange, }) } + onVisibilityChange={onVisibilityChange} onExtend={onExtend} /> ), - [context, isBuilt, isEditable, renderWidget, theme, moveWidget, onVisibilityChange], + [context, isBuilt, isEditable, isMobile, renderWidget, theme, moveWidget, onVisibilityChange], ); return (