From dd0a5fad643cd55c5f63f52806676d8be7d4a927 Mon Sep 17 00:00:00 2001 From: lby Date: Wed, 28 Aug 2024 10:50:08 +0800 Subject: [PATCH] feat(web): builtin infobox video and markdown block (#1117) --- server/pkg/builtin/manifest.yml | 42 ++++++++++++ server/pkg/builtin/manifest_ja.yml | 28 ++++++++ server/schemas/plugin_manifest.json | 5 +- web/package.json | 2 +- .../beta/components/DragAndDropList/Item.tsx | 2 +- .../SidePanelSectionField/index.tsx | 2 +- .../fields/CameraField/CapturePanel/index.tsx | 2 +- .../Widgets/ContainerSettingsPanel/index.tsx | 2 +- .../Infobox/Block/builtin/Image/index.tsx | 2 +- .../Infobox/Block/builtin/Markdown/index.tsx | 51 ++++++++++++++ .../Infobox/Block/builtin/Video/index.tsx | 68 +++++++++++++++++++ .../Crust/Infobox/Block/builtin/index.ts | 10 ++- .../Block/builtin/useExpressionEval.ts | 26 ++++--- .../Visualizer/Crust/Infobox/constants.ts | 4 ++ .../DragAndDropList/index.stories.tsx | 2 +- web/src/beta/pages/Dashboard/index.tsx | 4 +- web/src/beta/pages/EditorPage/index.tsx | 4 +- .../beta/pages/PluginPlaygroundPage/index.tsx | 2 +- .../beta/pages/ProjectSettingsPage/index.tsx | 4 +- web/yarn.lock | 10 +-- 20 files changed, 239 insertions(+), 33 deletions(-) create mode 100644 web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Markdown/index.tsx create mode 100644 web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Video/index.tsx diff --git a/server/pkg/builtin/manifest.yml b/server/pkg/builtin/manifest.yml index c37e6e02a5..6bcf6d0865 100644 --- a/server/pkg/builtin/manifest.yml +++ b/server/pkg/builtin/manifest.yml @@ -2709,3 +2709,45 @@ extensions: field: displayType type: string value: custom + - id: videoInfoboxBetaBlock + name: Video + type: infoboxBlock + description: Infobox Video Block + schema: + groups: + - id: panel + title: Panel Setting + fields: + - id: padding + type: spacing + title: Padding + ui: padding + min: 0 + max: 100 + - id: default + title: Video Block + fields: + - id: src + type: string + title: Video + - id: markdownInfoboxBetaBlock + name: MD Text + type: infoboxBlock + description: Infobox MD Text Block + schema: + groups: + - id: panel + title: Panel Setting + fields: + - id: padding + type: spacing + title: Padding + ui: padding + min: 0 + max: 100 + - id: default + title: MD Text Block + fields: + - id: src + type: string + title: Content diff --git a/server/pkg/builtin/manifest_ja.yml b/server/pkg/builtin/manifest_ja.yml index 7c54b474e2..cd6bfaf32c 100644 --- a/server/pkg/builtin/manifest_ja.yml +++ b/server/pkg/builtin/manifest_ja.yml @@ -1347,6 +1347,34 @@ extensions: custom: カスタム property_list: title: プロパティリスト + panel: + title: パネル設定 + fields: + padding: + title: 余白 + videoInfoboxBetaBlock: + name: ビデオ + description: インフォボックスビデオブロック + propertySchema: + default: + title: ビデオブロック + fields: + src: + title: ビデオ + panel: + title: パネル設定 + fields: + padding: + title: 余白 + markdownInfoboxBetaBlock: + name: MDテキスト + description: インフォボックスMDテキストブロック + propertySchema: + default: + title: MDテキストブロック + fields: + src: + title: コンテンツ panel: title: パネル設定 fields: diff --git a/server/schemas/plugin_manifest.json b/server/schemas/plugin_manifest.json index bcb2bed9a7..6cdbf013c4 100644 --- a/server/schemas/plugin_manifest.json +++ b/server/schemas/plugin_manifest.json @@ -336,7 +336,8 @@ "cluster", "story", "storyPage", - "storyBlock" + "storyBlock", + "infoboxBlock" ] }, "singleOnly": { @@ -454,4 +455,4 @@ } }, "$ref": "#/definitions/root" -} +} \ No newline at end of file diff --git a/web/package.json b/web/package.json index 99e70ae1b2..ae4d5e771d 100644 --- a/web/package.json +++ b/web/package.json @@ -164,7 +164,7 @@ "react-ga4": "2.1.0", "react-i18next": "12.2.2", "react-markdown": "8.0.7", - "react-player": "2.13.0", + "react-player": "2.16.0", "react-popper": "2.3.0", "react-router-dom": "6.21.0", "react-sortablejs": "6.1.4", diff --git a/web/src/beta/components/DragAndDropList/Item.tsx b/web/src/beta/components/DragAndDropList/Item.tsx index f5651613ae..d8c1b3f184 100644 --- a/web/src/beta/components/DragAndDropList/Item.tsx +++ b/web/src/beta/components/DragAndDropList/Item.tsx @@ -1,3 +1,4 @@ +import { styled } from "@reearth/services/theme"; import type { Identifier } from "dnd-core"; import type { FC, ReactNode } from "react"; import { memo, useRef, createContext, useContext } from "react"; @@ -8,7 +9,6 @@ import { useDrop, } from "react-dnd"; -import { styled } from "@reearth/services/theme"; type DragItem = { index: number; diff --git a/web/src/beta/components/SidePanelSectionField/index.tsx b/web/src/beta/components/SidePanelSectionField/index.tsx index 7470c8ff97..3f45a1cf05 100644 --- a/web/src/beta/components/SidePanelSectionField/index.tsx +++ b/web/src/beta/components/SidePanelSectionField/index.tsx @@ -1,6 +1,6 @@ +import { styled, useTheme } from "@reearth/services/theme"; import { useState, ReactNode, useEffect } from "react"; -import { styled, useTheme } from "@reearth/services/theme"; import Icon from "../Icon"; import Text from "../Text"; diff --git a/web/src/beta/components/fields/CameraField/CapturePanel/index.tsx b/web/src/beta/components/fields/CameraField/CapturePanel/index.tsx index 13ba0e0d22..1240e24895 100644 --- a/web/src/beta/components/fields/CameraField/CapturePanel/index.tsx +++ b/web/src/beta/components/fields/CameraField/CapturePanel/index.tsx @@ -1,4 +1,3 @@ -import { useMemo } from "react"; import Button from "@reearth/beta/components/Button"; import NumberInput from "@reearth/beta/components/fields/common/NumberInput"; @@ -6,6 +5,7 @@ import Text from "@reearth/beta/components/Text"; import { Camera } from "@reearth/beta/utils/value"; import { useT } from "@reearth/services/i18n"; import { styled } from "@reearth/services/theme"; +import { useMemo } from "react"; import PanelCommon from "../../common/PanelCommon"; import type { RowType } from "../types"; diff --git a/web/src/beta/features/Editor/Widgets/ContainerSettingsPanel/index.tsx b/web/src/beta/features/Editor/Widgets/ContainerSettingsPanel/index.tsx index 7e60ae1c42..1f11917d97 100644 --- a/web/src/beta/features/Editor/Widgets/ContainerSettingsPanel/index.tsx +++ b/web/src/beta/features/Editor/Widgets/ContainerSettingsPanel/index.tsx @@ -1,10 +1,10 @@ -import { FC } from "react"; import { ColorField, NumberField, SwitchField } from "@reearth/beta/ui/fields"; import { Panel } from "@reearth/beta/ui/layout"; import { useT } from "@reearth/services/i18n"; import { WidgetAreaPadding } from "@reearth/services/state"; import { styled } from "@reearth/services/theme"; +import { FC } from "react"; import { useWidgetsPage } from "../context"; diff --git a/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Image/index.tsx b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Image/index.tsx index 36d695e9fc..44c0124716 100644 --- a/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Image/index.tsx +++ b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Image/index.tsx @@ -38,6 +38,6 @@ export default ImageBlock; const Image = styled("img")(() => ({ width: "100%", height: "100%", - objectFit: "cover", + objectFit: "contain", objectPosition: "center", })); diff --git a/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Markdown/index.tsx b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Markdown/index.tsx new file mode 100644 index 0000000000..612d07f981 --- /dev/null +++ b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Markdown/index.tsx @@ -0,0 +1,51 @@ +import { FC, useMemo } from "react"; +import ReactMarkdown from "react-markdown"; +import gfm from "remark-gfm"; + +import BlockWrapper from "@reearth/beta/features/Visualizer/shared/components/BlockWrapper"; +import { CommonBlockProps } from "@reearth/beta/features/Visualizer/shared/types"; +import { ValueTypes } from "@reearth/beta/utils/value"; +import { styled } from "@reearth/services/theme"; + +import { InfoboxBlock } from "../../../types"; +import useExpressionEval from "../useExpressionEval"; + +const plugins = [gfm]; + +const MarkdownBlock: FC> = ({ block, isSelected, ...props }) => { + const src = useMemo( + () => block?.property?.default?.src?.value as ValueTypes["string"], + [block?.property?.default?.src], + ); + + const evaluatedSrc = useExpressionEval(src); + + return ( + + {evaluatedSrc !== undefined ? ( + + + {evaluatedSrc || ""} + + + ) : null} + + ); +}; + +export default MarkdownBlock; + +const Wrapper = styled("div")(() => ({ + width: "100%", + position: "relative", + ["*"]: { + maxWidth: "100%", + height: "auto", + }, +})); diff --git a/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Video/index.tsx b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Video/index.tsx new file mode 100644 index 0000000000..e1fe4a6a65 --- /dev/null +++ b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/Video/index.tsx @@ -0,0 +1,68 @@ +import { FC, useCallback, useMemo, useState } from "react"; +import Player from "react-player"; +import type ReactPlayer from "react-player"; + +import BlockWrapper from "@reearth/beta/features/Visualizer/shared/components/BlockWrapper"; +import { CommonBlockProps } from "@reearth/beta/features/Visualizer/shared/types"; +import { ValueTypes } from "@reearth/beta/utils/value"; +import { styled } from "@reearth/services/theme"; + +import { InfoboxBlock } from "../../../types"; +import useExpressionEval from "../useExpressionEval"; + +const VideoBlock: FC> = ({ block, isSelected, ...props }) => { + const [aspectRatio, setAspectRatio] = useState(56.25); + + const src = useMemo( + () => block?.property?.default?.src?.value as ValueTypes["string"], + [block?.property?.default?.src], + ); + + const evaluatedSrc = useExpressionEval(src); + + const handleVideoReady = useCallback((player: ReactPlayer) => { + const height = player.getInternalPlayer().videoHeight; + const width = player.getInternalPlayer().videoWidth; + if (!height || !width) return; + setAspectRatio((height / width) * 100); + }, []); + + return ( + + {evaluatedSrc !== undefined ? ( + + + + ) : null} + + ); +}; + +export default VideoBlock; + +const Wrapper = styled("div")<{ aspectRatio: number }>(({ aspectRatio }) => ({ + position: "relative", + paddingTop: `${aspectRatio}%`, + width: "100%", +})); + +const StyledPlayer = styled(Player)({ + position: "absolute", + top: 0, + left: 0, +}); diff --git a/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/index.ts b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/index.ts index a977357f10..d7b5bbe920 100644 --- a/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/index.ts +++ b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/index.ts @@ -5,16 +5,22 @@ import { IMAGE_BUILTIN_INFOBOX_BLOCK_ID, TEXT_BUILTIN_INFOBOX_BLOCK_ID, PROPERTY_BUILTIN_INFOBOX_BLOCK_ID, + VIDEO_BUILTIN_INFOBOX_BLOCK_ID, + MARKDOWN_BUILTIN_INFOBOX_BLOCK_ID, } from "../../constants"; import ImageBlock from "./Image"; +import MarkdownBlock from "./Markdown"; import PropertyListBlock from "./PropertyList"; import TextBlock from "./Text"; +import VideoBlock from "./Video"; export type ReEarthBuiltinInfoboxBlocks = Record< | typeof TEXT_BUILTIN_INFOBOX_BLOCK_ID | typeof PROPERTY_BUILTIN_INFOBOX_BLOCK_ID - | typeof IMAGE_BUILTIN_INFOBOX_BLOCK_ID, + | typeof IMAGE_BUILTIN_INFOBOX_BLOCK_ID + | typeof VIDEO_BUILTIN_INFOBOX_BLOCK_ID + | typeof MARKDOWN_BUILTIN_INFOBOX_BLOCK_ID, T >; @@ -25,6 +31,8 @@ const reearthBuiltin: BuiltinInfoboxBlocks = { [IMAGE_BUILTIN_INFOBOX_BLOCK_ID]: ImageBlock, [TEXT_BUILTIN_INFOBOX_BLOCK_ID]: TextBlock, [PROPERTY_BUILTIN_INFOBOX_BLOCK_ID]: PropertyListBlock, + [VIDEO_BUILTIN_INFOBOX_BLOCK_ID]: VideoBlock, + [MARKDOWN_BUILTIN_INFOBOX_BLOCK_ID]: MarkdownBlock, }; const builtin = merge({}, reearthBuiltin); diff --git a/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/useExpressionEval.ts b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/useExpressionEval.ts index 5cb3487fad..1e85127086 100644 --- a/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/useExpressionEval.ts +++ b/web/src/beta/features/Visualizer/Crust/Infobox/Block/builtin/useExpressionEval.ts @@ -10,12 +10,13 @@ export default (value: unknown | undefined) => { >(undefined); const [evaluatedResult, setEvaluatedResult] = useState( - undefined, + undefined ); const visualizer = useVisualizer(); // We want the useEffect to be called on each render to make sure evaluatedResult is up to date + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { if (!isReady) { setIsReady(true); @@ -26,7 +27,17 @@ export default (value: unknown | undefined) => { setEvaluatedResult(undefined); return; } - const selectedFeature = visualizer.current?.layers.selectedFeature(); + + // NOTE: core's selectedFeature is not always up to date, but the computed.features from selectedLayer is always up to date + // TODO: fix it on core + const selectedLayer = visualizer.current?.layers.selectedLayer(); + const selectedFeature = + selectedLayer?.type === "simple" && selectedLayer?.data?.isSketchLayer + ? selectedLayer.computed?.features.find( + (f) => f.id === visualizer.current?.layers.selectedFeature()?.id + ) + : visualizer.current?.layers.selectedFeature(); + if (selectedFeature && selectedFeature.id !== lastFeatureSelected) { setLastFeatureSelected(selectedFeature.id); setEvaluatedResult(undefined); @@ -45,7 +56,7 @@ export default (value: unknown | undefined) => { expression: currentValue, }, undefined, - simpleFeature, + simpleFeature ); if ( (es && typeof es === "string") || @@ -57,14 +68,7 @@ export default (value: unknown | undefined) => { } } } - }, [ - isReady, - currentValue, - value, - evaluatedResult, - visualizer, - lastFeatureSelected, - ]); + }); return evaluatedResult; }; diff --git a/web/src/beta/features/Visualizer/Crust/Infobox/constants.ts b/web/src/beta/features/Visualizer/Crust/Infobox/constants.ts index ed0cd2d03a..84276ba69f 100644 --- a/web/src/beta/features/Visualizer/Crust/Infobox/constants.ts +++ b/web/src/beta/features/Visualizer/Crust/Infobox/constants.ts @@ -10,9 +10,13 @@ export const IMAGE_BUILTIN_INFOBOX_BLOCK_ID = "reearth/imageInfoboxBetaBlock"; export const TEXT_BUILTIN_INFOBOX_BLOCK_ID = "reearth/textInfoboxBetaBlock"; export const PROPERTY_BUILTIN_INFOBOX_BLOCK_ID = "reearth/propertyInfoboxBetaBlock"; +export const VIDEO_BUILTIN_INFOBOX_BLOCK_ID = "reearth/videoInfoboxBetaBlock"; +export const MARKDOWN_BUILTIN_INFOBOX_BLOCK_ID = "reearth/markdownInfoboxBetaBlock"; export const AVAILABLE_INFOBOX_BLOCK_IDS = [ IMAGE_BUILTIN_INFOBOX_BLOCK_ID, TEXT_BUILTIN_INFOBOX_BLOCK_ID, PROPERTY_BUILTIN_INFOBOX_BLOCK_ID, + VIDEO_BUILTIN_INFOBOX_BLOCK_ID, + MARKDOWN_BUILTIN_INFOBOX_BLOCK_ID, ]; diff --git a/web/src/beta/lib/reearth-ui/components/DragAndDropList/index.stories.tsx b/web/src/beta/lib/reearth-ui/components/DragAndDropList/index.stories.tsx index c89122fd0b..820612b566 100644 --- a/web/src/beta/lib/reearth-ui/components/DragAndDropList/index.stories.tsx +++ b/web/src/beta/lib/reearth-ui/components/DragAndDropList/index.stories.tsx @@ -1,7 +1,7 @@ +import { styled } from "@reearth/services/theme"; import { Meta, Story } from "@storybook/react"; import { ReactNode, useState } from "react"; -import { styled } from "@reearth/services/theme"; import { Icon } from "../Icon"; diff --git a/web/src/beta/pages/Dashboard/index.tsx b/web/src/beta/pages/Dashboard/index.tsx index 4b94bb86c5..737cade7af 100644 --- a/web/src/beta/pages/Dashboard/index.tsx +++ b/web/src/beta/pages/Dashboard/index.tsx @@ -1,8 +1,8 @@ +import Dashboard from "@reearth/beta/features/Dashboard"; +import Page from "@reearth/beta/pages/Page"; import { FC } from "react"; import { useParams } from "react-router-dom"; -import Dashboard from "@reearth/beta/features/Dashboard"; -import Page from "@reearth/beta/pages/Page"; const DashboardPage: FC = () => { const { workspaceId } = useParams(); diff --git a/web/src/beta/pages/EditorPage/index.tsx b/web/src/beta/pages/EditorPage/index.tsx index 540f0fc582..88b8fbd2a0 100644 --- a/web/src/beta/pages/EditorPage/index.tsx +++ b/web/src/beta/pages/EditorPage/index.tsx @@ -1,10 +1,10 @@ -import { FC } from "react"; -import { useParams } from "react-router-dom"; import NotFound from "@reearth/beta/components/NotFound"; import Editor from "@reearth/beta/features/Editor"; import { isTab } from "@reearth/beta/features/Navbar"; import Page from "@reearth/beta/pages/Page"; +import { FC } from "react"; +import { useParams } from "react-router-dom"; const EditorPage: FC = () => { const { sceneId, tab } = useParams<{ sceneId: string; tab: string }>(); diff --git a/web/src/beta/pages/PluginPlaygroundPage/index.tsx b/web/src/beta/pages/PluginPlaygroundPage/index.tsx index a45e1f8402..5c7c1758d9 100644 --- a/web/src/beta/pages/PluginPlaygroundPage/index.tsx +++ b/web/src/beta/pages/PluginPlaygroundPage/index.tsx @@ -1,6 +1,6 @@ +import PluginPlayground from "@reearth/beta/features/PluginPlayground"; import { FC } from "react"; -import PluginPlayground from "@reearth/beta/features/PluginPlayground"; const PluginPlaygroundPage: FC = () => { return ; diff --git a/web/src/beta/pages/ProjectSettingsPage/index.tsx b/web/src/beta/pages/ProjectSettingsPage/index.tsx index a6245001ed..c65a7c3446 100644 --- a/web/src/beta/pages/ProjectSettingsPage/index.tsx +++ b/web/src/beta/pages/ProjectSettingsPage/index.tsx @@ -1,11 +1,11 @@ -import { FC, useMemo } from "react"; -import { useParams } from "react-router-dom"; import NotFound from "@reearth/beta/components/NotFound"; import ProjectSettings, { isProjectSettingTab, } from "@reearth/beta/features/ProjectSettings"; import Page from "@reearth/beta/pages/Page"; +import { FC, useMemo } from "react"; +import { useParams } from "react-router-dom"; const ProjectSettingsPage: FC = () => { const { projectId, tab, subId } = useParams<{ diff --git a/web/yarn.lock b/web/yarn.lock index 986a4d27d0..c7f57a7b46 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -8472,7 +8472,7 @@ __metadata: react-ga4: "npm:2.1.0" react-i18next: "npm:12.2.2" react-markdown: "npm:8.0.7" - react-player: "npm:2.13.0" + react-player: "npm:2.16.0" react-popper: "npm:2.3.0" react-router-dom: "npm:6.21.0" react-sortablejs: "npm:6.1.4" @@ -23644,9 +23644,9 @@ __metadata: languageName: node linkType: hard -"react-player@npm:2.13.0": - version: 2.13.0 - resolution: "react-player@npm:2.13.0" +"react-player@npm:2.16.0": + version: 2.16.0 + resolution: "react-player@npm:2.16.0" dependencies: deepmerge: "npm:^4.0.0" load-script: "npm:^1.0.0" @@ -23655,7 +23655,7 @@ __metadata: react-fast-compare: "npm:^3.0.1" peerDependencies: react: ">=16.6.0" - checksum: 10c0/3dc15908ffdab7c48788714ff806559b53bb80753024118ed2e31f8d0c857d83c04f9630bf1d195d4b0f621e3ce757f4dab9a51883fcc8c285250724edb0d9d9 + checksum: 10c0/ee0f4cd3bd468b28af38dbbb65cae1659218153956f43c0bdd15cd8b38aaaf151deec43b93b7dd887c0ac28242a4ba7e9d01823351e78ba60ae04d5fb51defb4 languageName: node linkType: hard