From 86dbb58f66e1b08c0e31dafcfc8f5d7f46118ed7 Mon Sep 17 00:00:00 2001 From: KaWaite <34051327+KaWaite@users.noreply.github.com> Date: Wed, 18 Oct 2023 16:21:32 +0900 Subject: [PATCH] chore(web): story panel refactor (#715) --- server/pkg/builtin/manifest.yml | 4 + .../beta/components/DragAndDropList/index.tsx | 4 +- web/src/beta/components/Overlay/index.tsx | 22 ---- .../components/fields/SpacingInput/index.tsx | 2 +- .../features/Editor/Visualizer/convert.ts | 99 ++++++++++++-- .../beta/features/Editor/Visualizer/hooks.ts | 12 +- .../beta/features/Editor/Visualizer/index.tsx | 3 +- .../RightPanel/ContainerSettings/index.tsx | 39 ++++-- .../beta/features/Editor/useStorytelling.ts | 25 ++-- .../Navbar/LeftSection/Profile/index.tsx | 38 +++--- .../Navbar/Menus/ProjectMenu/index.tsx | 34 ++--- .../Widget/builtin/Button/MenuButton.tsx | 87 +++++++------ .../Widget/builtin/Button/index.stories.tsx | 30 +++-- .../Widgets/Widget/builtin/Button/index.tsx | 2 +- .../Widgets/Widget/builtin/Navigator/hooks.ts | 2 +- .../Widgets/Widget/builtin/Timeline/hooks.ts | 2 +- .../lib/core/StoryPanel/ActionPanel/index.tsx | 28 +++- .../Block/builtin/Camera/Editor.tsx | 63 +++++---- .../StoryPanel/Block/builtin/Camera/index.tsx | 60 ++++----- .../StoryPanel/Block/builtin/Image/index.tsx | 10 +- .../Block/builtin/Markdown/index.tsx | 19 ++- .../StoryPanel/Block/builtin/Text/index.tsx | 19 ++- .../StoryPanel/Block/builtin/Title/index.tsx | 23 ++-- .../Block/builtin/Video/VideoPlayer.tsx | 9 +- .../StoryPanel/Block/builtin/Video/index.tsx | 11 +- .../builtin/common/ActionPanel/index.tsx | 4 +- .../Block/builtin/common/Wrapper.tsx | 39 ++++-- .../StoryPanel/Block/builtin/common/hooks.ts | 55 +++++--- .../builtin/common/useActionPropertyApi.ts | 2 - .../beta/lib/core/StoryPanel/Block/types.ts | 35 +---- .../beta/lib/core/StoryPanel/Page/hooks.ts | 70 +++++----- .../beta/lib/core/StoryPanel/Page/index.tsx | 84 ++++++------ .../lib/core/StoryPanel/PanelContent/hooks.ts | 6 +- .../core/StoryPanel/PanelContent/index.tsx | 4 +- .../core/StoryPanel/SelectableArea/hooks.ts | 15 +-- .../core/StoryPanel/SelectableArea/index.tsx | 7 +- web/src/beta/lib/core/StoryPanel/hooks.ts | 7 +- .../StoryPanel/hooks/useFieldComponent.tsx | 121 ++++++++++++++++++ web/src/beta/lib/core/StoryPanel/index.tsx | 2 +- web/src/beta/lib/core/StoryPanel/types.ts | 25 ++-- web/src/beta/lib/core/StoryPanel/utils.ts | 30 ++++- web/src/beta/utils/published.ts | 29 +++++ web/src/services/api/propertyApi/utils.ts | 2 +- .../services/api/storytellingApi/blocks.ts | 4 +- web/src/services/i18n/translations/en.yml | 24 ++-- web/src/services/i18n/translations/ja.yml | 30 +++-- 46 files changed, 777 insertions(+), 465 deletions(-) delete mode 100644 web/src/beta/components/Overlay/index.tsx create mode 100644 web/src/beta/lib/core/StoryPanel/hooks/useFieldComponent.tsx create mode 100644 web/src/beta/utils/published.ts diff --git a/server/pkg/builtin/manifest.yml b/server/pkg/builtin/manifest.yml index 1295b3da1b..ecf07df179 100644 --- a/server/pkg/builtin/manifest.yml +++ b/server/pkg/builtin/manifest.yml @@ -1839,6 +1839,7 @@ extensions: title: Duration suffix: s min: 0 + defaultValue: 2 - id: cameraDelay type: number title: Delay @@ -2033,10 +2034,12 @@ extensions: ui: padding min: 0 max: 100 + defaultValue: { top: 20, bottom: 20, left: 20, right: 20 } - id: gap title: Gap type: number suffix: px + defaultValue: 20 - id: title title: Title Setting fields: @@ -2058,6 +2061,7 @@ extensions: title: Duration suffix: s min: 0 + defaultValue: 2 - id: cameraDelay type: number title: Delay diff --git a/web/src/beta/components/DragAndDropList/index.tsx b/web/src/beta/components/DragAndDropList/index.tsx index d8fadc0f63..071f33d49f 100644 --- a/web/src/beta/components/DragAndDropList/index.tsx +++ b/web/src/beta/components/DragAndDropList/index.tsx @@ -13,7 +13,7 @@ export type Props = { getId: (item: Item) => string; onItemDrop(item: Item, targetIndex: number): void; renderItem: (item: Item, index: number) => ReactNode; - gap: number; + gap?: number; }; function DragAndDropList({ @@ -98,5 +98,5 @@ export default DragAndDropList; const SWrapper = styled.div>` display: flex; flex-direction: column; - ${({ gap }) => `gap: ${gap}px`} + ${({ gap }) => gap && `gap: ${gap}px;`} `; diff --git a/web/src/beta/components/Overlay/index.tsx b/web/src/beta/components/Overlay/index.tsx deleted file mode 100644 index 442766dde3..0000000000 --- a/web/src/beta/components/Overlay/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { styled } from "@reearth/services/theme"; - -type Props = { - show?: boolean; - strong?: boolean; -}; - -const Overlay = styled.div` - position: absolute; - width: 100%; - height: 100%; - background: ${({ theme }) => theme.bg[1]}; - opacity: ${({ show, strong }) => (show ? (strong ? 0.7 : 0.3) : 0)}; - visibility: ${({ show }) => (show ? "visible" : "hidden")}; - transition: all 0.2s; - transition-timing-function: ${({ show }) => (show ? "ease-in" : "ease-out")}; - z-index: 1; - top: 0; - left: 0; -`; - -export default Overlay; diff --git a/web/src/beta/components/fields/SpacingInput/index.tsx b/web/src/beta/components/fields/SpacingInput/index.tsx index 9995551873..05b18db798 100644 --- a/web/src/beta/components/fields/SpacingInput/index.tsx +++ b/web/src/beta/components/fields/SpacingInput/index.tsx @@ -47,9 +47,9 @@ const SpacingInput: React.FC = ({ name, description, value, min, max, onC {SPACING_POSITIONS.map((position, index) => ( { + if (!story) return undefined; + + const storyPages = (pages: GqlStoryPage[]): StoryPage[] => + pages.map(p => ({ + id: p.id, + title: p.title, + propertyId: p.propertyId, + property: processProperty(undefined, p.property), + blocks: storyBlocks(p.blocks), + })); + + const storyBlocks = (blocks: GqlStoryBlock[]): StoryBlock[] => + blocks.map(b => ({ + id: b.id, + pluginId: b.pluginId, + extensionId: b.extensionId, + name: b.property?.schema?.groups.find(g => g.schemaGroupId === "default")?.title, + propertyId: b.property?.id, + property: processProperty(undefined, b.property), + })); + + return { + id: story.id, + title: story.title, + pages: storyPages(story.pages), + }; +}; + export const processProperty = ( parent: PropertyFragmentFragment | null | undefined, orig?: PropertyFragmentFragment | null | undefined, @@ -204,11 +238,20 @@ export const processProperty = ( }), {}, ); - const mergedProperty: P = Object.fromEntries( Object.entries(allItems) .map(([key, value]) => { const { schema, orig, parent } = value; + if (!orig && !parent) { + if (schema.isList) { + return [key, undefined]; + } + return [ + key, + processPropertyGroups(schema, undefined, undefined, linkedDatasetId, datasets), + ]; + } + if ( (!orig || orig.__typename === "PropertyGroupList") && (!parent || parent.__typename === "PropertyGroupList") @@ -264,20 +307,48 @@ const processPropertyGroups = ( ); return Object.fromEntries( - Object.entries(allFields) - .map(([key, { parent, orig }]) => { - const used = orig || parent; - if (!used) return [key, null]; - - const datasetSchemaId = used?.links?.[0]?.datasetSchemaId; - const datasetFieldId = used?.links?.[0]?.datasetSchemaFieldId; - if (datasetSchemaId && linkedDatasetId && datasetFieldId) { - return [key, datasetValue(datasets, datasetSchemaId, linkedDatasetId, datasetFieldId)]; - } + Object.entries(allFields).map(([key, { schema, parent, orig }]) => { + const used = orig || parent; - return [key, valueFromGQL(used.value, used.type)?.value]; - }) - .filter(([, value]) => typeof value !== "undefined" && value !== null), + const fieldMeta = { + type: valueTypeFromGQL(schema.type) || undefined, + ui: toUi(schema.ui) || undefined, + title: schema.translatedTitle || undefined, + description: schema.translatedDescription || undefined, + }; + + if (!used) { + return [ + key, + { + ...fieldMeta, + value: schema.defaultValue + ? valueFromGQL(schema.defaultValue, schema.type)?.value + : undefined, + }, + ]; + } + + const datasetSchemaId = used?.links?.[0]?.datasetSchemaId; + const datasetFieldId = used?.links?.[0]?.datasetSchemaFieldId; + if (datasetSchemaId && linkedDatasetId && datasetFieldId) { + return [ + key, + { + ...fieldMeta, + value: datasetValue(datasets, datasetSchemaId, linkedDatasetId, datasetFieldId), + }, + ]; + } + + return [ + key, + { + ...fieldMeta, + value: valueFromGQL(used.value, used.type)?.value, + }, + ]; + }), ); }; diff --git a/web/src/beta/features/Editor/Visualizer/hooks.ts b/web/src/beta/features/Editor/Visualizer/hooks.ts index 9529bcdd1d..40170f1bc2 100644 --- a/web/src/beta/features/Editor/Visualizer/hooks.ts +++ b/web/src/beta/features/Editor/Visualizer/hooks.ts @@ -25,19 +25,19 @@ import { isVisualizerReadyVar, } from "@reearth/services/state"; -import { convertWidgets, processLayers } from "./convert"; +import { convertStory, convertWidgets, processLayers } from "./convert"; import type { BlockType } from "./type"; export default ({ sceneId, - isBuilt, storyId, + isBuilt, currentPage, showStoryPanel, }: { sceneId?: string; - isBuilt?: boolean; storyId?: string; + isBuilt?: boolean; currentPage?: Page; showStoryPanel?: boolean; }) => { @@ -204,6 +204,11 @@ export default ({ [sceneId, useUpdateWidgetAlignSystem], ); + const story = useMemo( + () => convertStory(scene?.stories.find(s => s.id === storyId)), + [storyId, scene?.stories], + ); + const handleStoryBlockCreate = useCallback( async (pageId?: string, extensionId?: string, pluginId?: string, index?: number) => { if (!extensionId || !pluginId || !storyId || !pageId) return; @@ -264,6 +269,7 @@ export default ({ tags, widgets, layers, + story, blocks, isCapturing, sceneMode, diff --git a/web/src/beta/features/Editor/Visualizer/index.tsx b/web/src/beta/features/Editor/Visualizer/index.tsx index ed7ca20206..6cfb121880 100644 --- a/web/src/beta/features/Editor/Visualizer/index.tsx +++ b/web/src/beta/features/Editor/Visualizer/index.tsx @@ -52,6 +52,7 @@ const Visualizer: React.FC = ({ clusters, layers, widgets, + story, tags, selectedLayerId, blocks, @@ -132,7 +133,7 @@ const Visualizer: React.FC = ({ renderInfoboxInsertionPopup={renderInfoboxInsertionPopUp}> {showStoryPanel && ( = ({ widgetArea, onWidgetAreaStateChange }) => { - // TODO: This is dummy UI + const t = useT(); + return ( { onWidgetAreaStateChange({ @@ -25,7 +29,7 @@ const ContainerSettings: React.FC = ({ widgetArea, onWidgetAreaStateChang }} /> { onWidgetAreaStateChange({ @@ -38,7 +42,7 @@ const ContainerSettings: React.FC = ({ widgetArea, onWidgetAreaStateChang }} /> { onWidgetAreaStateChange({ @@ -51,7 +55,7 @@ const ContainerSettings: React.FC = ({ widgetArea, onWidgetAreaStateChang }} /> { onWidgetAreaStateChange({ @@ -65,7 +69,7 @@ const ContainerSettings: React.FC = ({ widgetArea, onWidgetAreaStateChang /> { onWidgetAreaStateChange({ @@ -74,9 +78,26 @@ const ContainerSettings: React.FC = ({ widgetArea, onWidgetAreaStateChang }); }} /> - -
[Switch field] Align Center {!!widgetArea?.centered}
-
[Color field] Background Color {widgetArea?.background}
+ { + onWidgetAreaStateChange({ + ...widgetArea, + centered: newVal, + }); + }} + /> + { + onWidgetAreaStateChange({ + ...widgetArea, + background: newVal, + }); + }} + />
); }; diff --git a/web/src/beta/features/Editor/useStorytelling.ts b/web/src/beta/features/Editor/useStorytelling.ts index c6c6b17eb0..d0ab763a0f 100644 --- a/web/src/beta/features/Editor/useStorytelling.ts +++ b/web/src/beta/features/Editor/useStorytelling.ts @@ -18,7 +18,6 @@ const getPage = (id?: string, pages?: Page[]) => { export default function ({ sceneId, onFlyTo }: Props) { const t = useT(); - const timeoutRef = useRef(); const { useStoriesQuery, @@ -58,18 +57,24 @@ export default function ({ sceneId, onFlyTo }: Props) { isAutoScrolling.current = true; element?.scrollIntoView({ behavior: "smooth" }); } - const camera = newPage.property.items?.find(i => i.schemaGroup === "cameraAnimation"); - if (camera && "fields" in camera) { - const destination = camera.fields.find(f => f.id === "cameraPosition")?.value as Camera; + const cameraFieldGroup = newPage.property.items?.find( + i => i.schemaGroup === "cameraAnimation", + ); + const schemaFields = cameraFieldGroup?.schemaFields; + + let destination = schemaFields?.find(sf => sf.id === "cameraPosition") + ?.defaultValue as Camera; + let duration = schemaFields?.find(sf => sf.id === "cameraDuration")?.defaultValue as number; + + if (cameraFieldGroup && "fields" in cameraFieldGroup) { + destination = (cameraFieldGroup.fields.find(f => f.id === "cameraPosition")?.value ?? + destination) as Camera; if (!destination) return; - const duration = camera.fields.find(f => f.id === "cameraDuration")?.value as number; - const delay = (camera.fields.find(f => f.id === "cameraDelay")?.value ?? 0) as number; + duration = (cameraFieldGroup.fields.find(f => f.id === "cameraDuration")?.value ?? + duration) as number; - if (timeoutRef.current) clearTimeout(timeoutRef.current); - timeoutRef.current = setTimeout(() => { - onFlyTo({ ...destination }, { duration }); - }, delay * 1000); + onFlyTo({ ...destination }, { duration }); } }, [selectedStory?.pages, onFlyTo], diff --git a/web/src/beta/features/Navbar/LeftSection/Profile/index.tsx b/web/src/beta/features/Navbar/LeftSection/Profile/index.tsx index 6e0e9d716b..0e25022381 100644 --- a/web/src/beta/features/Navbar/LeftSection/Profile/index.tsx +++ b/web/src/beta/features/Navbar/LeftSection/Profile/index.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState } from "react"; +import { Fragment, useCallback, useState } from "react"; import { Link } from "react-router-dom"; import Icon, { Icons } from "@reearth/beta/components/Icon"; @@ -52,23 +52,25 @@ const Menu: React.FC = ({ label, items, nested }) => { - {items?.map(({ text: value, linkTo, breakpoint, icon, onClick, items }, index) => - breakpoint ? ( - - ) : items ? ( - - ) : linkTo ? ( - - {icon && } - {value} - - ) : ( - - ), - )} + {items?.map(({ text: value, linkTo, breakpoint, icon, onClick, items }, index) => ( + + {breakpoint ? ( + + ) : items ? ( + + ) : linkTo ? ( + + {icon && } + {value} + + ) : ( + + )} + + ))} ); diff --git a/web/src/beta/features/Navbar/Menus/ProjectMenu/index.tsx b/web/src/beta/features/Navbar/Menus/ProjectMenu/index.tsx index 65dbe7d40a..aaa87e76f4 100644 --- a/web/src/beta/features/Navbar/Menus/ProjectMenu/index.tsx +++ b/web/src/beta/features/Navbar/Menus/ProjectMenu/index.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback } from "react"; +import { useState, useCallback, Fragment } from "react"; import { Link } from "react-router-dom"; import Icon, { Icons } from "@reearth/beta/components/Icon"; @@ -82,21 +82,23 @@ const ProjectMenu: React.FC = ({ currentProject, workspaceId }) => { - {menuItems?.map(({ text: value, linkTo, breakpoint, icon, onClick }, index) => - breakpoint ? ( - - ) : linkTo ? ( - - {icon && } - {value} - - ) : ( - - ), - )} + {menuItems?.map(({ text: value, linkTo, breakpoint, icon, onClick }, index) => ( + + {breakpoint ? ( + + ) : linkTo ? ( + + {icon && } + {value} + + ) : ( + + )} + + ))} ); diff --git a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/MenuButton.tsx b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/MenuButton.tsx index 8e385661f3..5fbb71958a 100644 --- a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/MenuButton.tsx +++ b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/MenuButton.tsx @@ -4,6 +4,7 @@ import { usePopper } from "react-popper"; import Flex from "@reearth/beta/components/Flex"; import Icon from "@reearth/beta/components/Icon"; import Text from "@reearth/beta/components/Text"; +import type { Field } from "@reearth/beta/lib/core/StoryPanel/types"; import { styled, metricsSizes, mask } from "@reearth/services/theme"; import type { Camera, FlyToDestination, Theme } from "../../types"; @@ -11,24 +12,24 @@ import { Visible } from "../../useVisible"; export type Button = { id: string; - buttonType?: "menu" | "link" | "camera"; - buttonTitle?: string; - buttonStyle?: "text" | "icon" | "texticon"; - buttonIcon?: string; - buttonLink?: string; - buttonColor?: string; - buttonBgcolor?: string; - buttonCamera?: Camera; - visible: Visible; + buttonType?: Field<"menu" | "link" | "camera">; + buttonTitle?: Field; + buttonStyle?: Field<"text" | "icon" | "texticon">; + buttonIcon?: Field; + buttonLink?: Field; + buttonColor?: Field; + buttonBgcolor?: Field; + buttonCamera?: Field; + visible: Field; }; export type MenuItem = { id: string; - menuTitle?: string; - menuIcon?: string; - menuType?: "link" | "camera" | "border"; - menuLink?: string; - menuCamera?: Camera; + menuTitle?: Field; + menuIcon?: Field; + menuType?: Field<"link" | "camera" | "border">; + menuLink?: Field; + menuCamera?: Field; }; export type Props = { @@ -92,18 +93,24 @@ export default function MenuButton({ const handleClick = useCallback( (b: Button | MenuItem) => () => { - const t = "buttonType" in b ? b.buttonType : "menuType" in b ? b.menuType : undefined; + const t = + "buttonType" in b ? b.buttonType?.value : "menuType" in b ? b.menuType?.value : undefined; if (t === "menu") { setVisibleMenuButton(!visibleMenuButton); return; } else if (t === "camera") { const camera = - "buttonCamera" in b ? b.buttonCamera : "menuCamera" in b ? b.menuCamera : undefined; + "buttonCamera" in b + ? b.buttonCamera?.value + : "menuCamera" in b + ? b.menuCamera?.value + : undefined; if (camera) { onFlyTo?.(camera, { duration: 2 }); } } else { - let link = "buttonLink" in b ? b.buttonLink : "menuLink" in b ? b.menuLink : undefined; + let link = + "buttonLink" in b ? b.buttonLink?.value : "menuLink" in b ? b.menuLink?.value : undefined; if (link) { const splitLink = link?.split("/"); if (splitLink?.[0] !== "http:" && splitLink?.[0] !== "https:") { @@ -124,17 +131,16 @@ export default function MenuButton({ button={b} onClick={b && handleClick(b)} ref={referenceElement}> - {(b?.buttonStyle === "icon" || b?.buttonStyle === "texticon") && b?.buttonIcon && ( - - )} - {b?.buttonStyle !== "icon" && ( + {(b?.buttonStyle?.value === "icon" || b?.buttonStyle?.value === "texticon") && + b?.buttonIcon && } + {b?.buttonStyle?.value !== "icon" && ( - {b?.buttonTitle} + {b?.buttonTitle?.value} )} @@ -150,14 +156,14 @@ export default function MenuButton({ button={b} onClick={handleClick(i)}> - {i.menuIcon && } + {i.menuIcon && } - {i.menuTitle} + {i.menuTitle?.value} @@ -173,7 +179,8 @@ const Wrapper = styled.div<{ button?: Button; publishedTheme?: Theme }>` border-radius: ${metricsSizes["xs"]}px; &, > div { - background-color: ${({ button, publishedTheme }) => button?.buttonBgcolor || publishedTheme}; + background-color: ${({ button, publishedTheme }) => + button?.buttonBgcolor?.value || publishedTheme}; } `; @@ -185,14 +192,14 @@ const Button = styled.div<{ button?: Button; publishedTheme?: Theme }>` padding: 0 10px; line-height: 35px; box-sizing: border-box; - color: ${({ button, publishedTheme }) => button?.buttonColor || publishedTheme?.mainText}; + color: ${({ button, publishedTheme }) => button?.buttonColor?.value || publishedTheme?.mainText}; cursor: pointer; align-items: center; user-select: none; &:hover { background: ${({ publishedTheme, button }) => - mask(button?.buttonBgcolor) || publishedTheme?.mask}; + mask(button?.buttonBgcolor?.value) || publishedTheme?.mask}; } `; @@ -207,7 +214,7 @@ const MenuWrapper = styled.div` const MenuInnerWrapper = styled.div<{ button?: Button; publishedTheme?: Theme }>` min-width: 35px; width: 100%; - color: ${({ button, publishedTheme }) => button?.buttonColor || publishedTheme?.mainText}; + color: ${({ button, publishedTheme }) => button?.buttonColor?.value || publishedTheme?.mainText}; white-space: nowrap; `; @@ -216,20 +223,24 @@ const MenuItem = styled(Flex)<{ button?: Button; publishedTheme?: Theme; }>` - min-height: ${({ item }) => (item?.menuType === "border" ? null : "25px")}; - border-radius: ${({ item }) => (item?.menuType === "border" ? null : "3px")}; - padding: ${({ item }) => (item?.menuType === "border" ? "0 10px" : "2px 10px")}; - cursor: ${({ item }) => (item?.menuType === "border" ? null : "pointer")}; + min-height: ${({ item }) => (item?.menuType?.value === "border" ? null : "25px")}; + border-radius: ${({ item }) => (item?.menuType?.value === "border" ? null : "3px")}; + padding: ${({ item }) => (item?.menuType?.value === "border" ? "0 10px" : "2px 10px")}; + cursor: ${({ item }) => (item?.menuType?.value === "border" ? null : "pointer")}; background: ${({ publishedTheme, item, button }) => - item?.menuType === "border" ? mask(button?.buttonBgcolor) || publishedTheme?.mask : null}; + item?.menuType?.value === "border" + ? mask(button?.buttonBgcolor?.value) || publishedTheme?.mask + : null}; border-bottom: ${({ item, publishedTheme, button }) => - item?.menuType === "border" - ? `1px solid ${button?.buttonColor || publishedTheme?.weakText}` + item?.menuType?.value === "border" + ? `1px solid ${button?.buttonColor?.value || publishedTheme?.weakText}` : null}; user-select: none; &:hover { background: ${({ publishedTheme, item, button }) => - item?.menuType === "border" ? null : mask(button?.buttonBgcolor) || publishedTheme?.mask}; + item?.menuType?.value === "border" + ? null + : mask(button?.buttonBgcolor?.value) || publishedTheme?.mask}; } `; diff --git a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/index.stories.tsx b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/index.stories.tsx index 789a44b485..94d61e2459 100644 --- a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/index.stories.tsx +++ b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/index.stories.tsx @@ -19,27 +19,31 @@ Default.args = { menu: [ { id: "hoge", - menuTitle: "Hoge", - menuType: "camera", + menuTitle: { + value: "Hoge", + }, + menuType: { value: "camera" }, menuCamera: { - lat: 0, - lng: 0, - height: 1000, - fov: CesiumMath.toRadians(60), - heading: 0, - pitch: 0, - roll: 0, + value: { + lat: 0, + lng: 0, + height: 1000, + fov: CesiumMath.toRadians(60), + heading: 0, + pitch: 0, + roll: 0, + }, }, }, { id: "hoge", - menuType: "border", + menuType: { value: "border" }, }, { id: "GitHub", - menuType: "link", - menuTitle: "GitHub", - menuLink: "https://github.com", + menuType: { value: "link" }, + menuTitle: { value: "GitHub" }, + menuLink: { value: "https://github.com" }, }, ], }, diff --git a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/index.tsx b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/index.tsx index d7a1105f04..c64bf94832 100644 --- a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/index.tsx +++ b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Button/index.tsx @@ -23,7 +23,7 @@ const Menu = ({ const { default: button, menu: menuItems } = widget.property ?? {}; const visible = useVisible({ widgetId: widget.id, - visible: widget.property?.default?.visible, + visible: widget.property?.default?.visible?.value, isMobile, onVisibilityChange, }); diff --git a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Navigator/hooks.ts b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Navigator/hooks.ts index 0ff0bbf0df..6dee99c2d0 100644 --- a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Navigator/hooks.ts +++ b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Navigator/hooks.ts @@ -34,7 +34,7 @@ export default function ({ const isMovingOrbit = useRef(false); const visible = useVisible({ widgetId: widget.id, - visible: widget.property?.default?.visible, + visible: widget.property?.default?.visible?.value, isMobile, onVisibilityChange, }); diff --git a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Timeline/hooks.ts b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Timeline/hooks.ts index 35e109d484..6b8d29c587 100644 --- a/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Timeline/hooks.ts +++ b/web/src/beta/lib/core/Crust/Widgets/Widget/builtin/Timeline/hooks.ts @@ -47,7 +47,7 @@ export const useTimeline = ({ }) => { const visible = useVisible({ widgetId: widget.id, - visible: widget.property?.default?.visible, + visible: widget.property?.default?.visible?.value, isMobile, onVisibilityChange, }); diff --git a/web/src/beta/lib/core/StoryPanel/ActionPanel/index.tsx b/web/src/beta/lib/core/StoryPanel/ActionPanel/index.tsx index 43085fb4f5..f51e7a7873 100644 --- a/web/src/beta/lib/core/StoryPanel/ActionPanel/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/ActionPanel/index.tsx @@ -1,16 +1,16 @@ import { Dispatch, Fragment, MouseEvent, SetStateAction, useCallback, useMemo } from "react"; import { useItemContext } from "@reearth/beta/components/DragAndDropList/Item"; -import FieldComponents from "@reearth/beta/components/fields/PropertyFields"; import Icon, { Icons } from "@reearth/beta/components/Icon"; import * as Popover from "@reearth/beta/components/Popover"; import PopoverMenuContent from "@reearth/beta/components/PopoverMenuContent"; import Text from "@reearth/beta/components/Text"; import { stopClickPropagation } from "@reearth/beta/utils/events"; -import type { Item } from "@reearth/services/api/propertyApi/utils"; import { useT } from "@reearth/services/i18n"; import { styled } from "@reearth/services/theme"; +import { FieldComponent } from "../hooks/useFieldComponent"; + export type ActionItem = { icon: string; name?: string; @@ -25,7 +25,7 @@ type Props = { showSettings?: boolean; showPadding?: boolean; propertyId?: string; - panelSettings?: Item; + panelSettings?: any; actionItems: ActionItem[]; dndEnabled?: boolean; position?: ActionPosition; @@ -56,10 +56,12 @@ const ActionPanel: React.FC = ({ onSettingsToggle?.(); }, [onRemove, onSettingsToggle]); + const settingsTitle = useMemo(() => t("Spacing settings"), [t]); + const popoverContent = useMemo(() => { const menuItems: { name: string; icon: Icons; onClick: () => void }[] = [ { - name: t("Padding settings"), + name: settingsTitle, icon: "padding", onClick: () => setShowPadding(true), }, @@ -72,7 +74,7 @@ const ActionPanel: React.FC = ({ }); } return menuItems; - }, [t, setShowPadding, onRemove, handleRemove]); + }, [settingsTitle, t, setShowPadding, onRemove, handleRemove]); return ( @@ -111,13 +113,25 @@ const ActionPanel: React.FC = ({ - {panelSettings?.title} + {settingsTitle} setShowPadding(false)} /> {propertyId && panelSettings && ( - + {Object.keys(panelSettings).map(fieldId => { + const field = panelSettings[fieldId]; + const groupId = "panel"; + return ( + + ); + })} )} diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Camera/Editor.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Camera/Editor.tsx index 7ec443067f..a6ed437217 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Camera/Editor.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Camera/Editor.tsx @@ -1,6 +1,6 @@ import { useReactiveVar } from "@apollo/client"; import { debounce } from "lodash-es"; -import { useContext, useMemo, useState } from "react"; +import { useCallback, useContext, useMemo, useState } from "react"; import Button from "@reearth/beta/components/Button"; import CameraField from "@reearth/beta/components/fields/CameraField"; @@ -13,14 +13,15 @@ import { useT } from "@reearth/services/i18n"; import { currentCameraVar } from "@reearth/services/state"; import { styled } from "@reearth/services/theme"; +import type { Field } from "../../../types"; import { BlockContext } from "../common/Wrapper"; -type CameraBlock = { +export type CameraBlock = { id: string; - title?: string; - color?: string; - bgColor?: string; - cameraPosition?: Camera; + title?: Field; + color?: Field; + bgColor?: Field; + cameraPosition?: Field; }; export type Props = { @@ -51,24 +52,28 @@ const CameraBlockEditor: React.FC = ({ const visualizer = useVisualizer(); const currentCamera = useReactiveVar(currentCameraVar); + const handleFlyTo = useMemo(() => visualizer.current?.engine.flyTo, [visualizer]); const editorProperties = useMemo(() => items.find(i => i.id === selected), [items, selected]); - const handleClick = (itemId: string) => { - if (inEditor) { - setSelected(itemId); - return; - } - const item = items.find(i => i.id === itemId); - if (!item?.cameraPosition) return; - handleFlyTo?.(item.cameraPosition); - }; + const handleClick = useCallback( + (itemId: string) => { + if (inEditor) { + setSelected(itemId); + return; + } + const item = items.find(i => i.id === itemId); + if (!item?.cameraPosition?.value) return; + handleFlyTo?.(item.cameraPosition?.value); + }, + [items, inEditor, handleFlyTo], + ); const debounceOnUpdate = useMemo(() => debounce(onUpdate, 500), [onUpdate]); const listItems = useMemo( - () => items.map(({ id, title }) => ({ id, value: title ?? "New Camera" })), + () => items.map(({ id, title }) => ({ id, value: title?.value ?? "New Camera" })), [items], ); @@ -79,11 +84,11 @@ const CameraBlockEditor: React.FC = ({ return ( handleClick(id)} /> @@ -104,25 +109,29 @@ const CameraBlockEditor: React.FC = ({ /> onUpdate(selected, "cameraPosition", "camera", value as Camera)} currentCamera={currentCamera} onFlyTo={handleFlyTo} /> debounceOnUpdate(selected, "title", "string", value)} /> debounceOnUpdate(selected, "color", "string", value)} /> debounceOnUpdate(selected, "bgColor", "string", value)} /> diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Camera/index.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Camera/index.tsx index 447089d73e..4b07e0126a 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Camera/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Camera/index.tsx @@ -1,13 +1,12 @@ import { useCallback, useEffect, useMemo } from "react"; -import { ValueTypes } from "@reearth/beta/utils/value"; +import { type ValueTypes } from "@reearth/beta/utils/value"; -import { getFieldValue } from "../../../utils"; -import { CommonProps as BlockProps } from "../../types"; +import type { CommonProps as BlockProps } from "../../types"; import usePropertyValueUpdate from "../common/useActionPropertyApi"; import BlockWrapper from "../common/Wrapper"; -import CameraEditor, { Props as EditorProps } from "./Editor"; +import CameraEditor, { type CameraBlock as CameraBlockType } from "./Editor"; export type Props = BlockProps; @@ -19,9 +18,9 @@ const CameraBlock: React.FC = ({ block, isSelected, ...props }) => { handleMovePropertyItem, } = usePropertyValueUpdate(); - const items = useMemo( - () => getFieldValue(block?.property?.items ?? [], "") as EditorProps["items"], - [block?.property?.items], + const cameraButtons = useMemo( + () => Object.values(block?.property?.default) as CameraBlockType[], + [block?.property?.default], ); const handleUpdate = useCallback( @@ -31,71 +30,62 @@ const CameraBlock: React.FC = ({ block, isSelected, ...props }) => { fieldType: keyof ValueTypes, updatedValue: ValueTypes[keyof ValueTypes], ) => { - const schemaGroup = block?.property?.items?.find( - i => i.schemaGroup === "default", - )?.schemaGroup; - if (!block?.property?.id || !itemId || !schemaGroup) return; + if (!block?.propertyId || !itemId) return; handlePropertyValueUpdate( - schemaGroup, - block?.property?.id, + "default", + block?.propertyId, fieldId, fieldType, itemId, )(updatedValue); }, - [block?.property?.id, block?.property?.items, handlePropertyValueUpdate], + [block?.propertyId, handlePropertyValueUpdate], ); const handleItemAdd = useCallback(() => { - const schemaGroup = block?.property?.items?.find(i => i.schemaGroup === "default")?.schemaGroup; - if (!block?.property?.id || !schemaGroup) return; - handleAddPropertyItem(block.property.id, schemaGroup); - }, [block?.property?.id, block?.property?.items, handleAddPropertyItem]); + if (!block?.propertyId) return; + handleAddPropertyItem(block.propertyId, "default"); + }, [block?.propertyId, handleAddPropertyItem]); const handleItemRemove = useCallback( (itemId: string) => { - const schemaGroup = block?.property?.items?.find( - i => i.schemaGroup === "default", - )?.schemaGroup; - if (!block?.property?.id || !itemId || !schemaGroup) return; + if (!block?.propertyId || !itemId) return; - handleRemovePropertyItem(block.property.id, schemaGroup, itemId); + handleRemovePropertyItem(block.propertyId, "default", itemId); }, - [block?.property?.id, block?.property?.items, handleRemovePropertyItem], + [block?.propertyId, handleRemovePropertyItem], ); const handleItemMove = useCallback( ({ id }: { id: string }, index: number) => { - const schemaGroup = block?.property?.items?.find( - i => i.schemaGroup === "default", - )?.schemaGroup; - if (!block?.property?.id || !id || !schemaGroup) return; + if (!block?.propertyId || !id) return; - handleMovePropertyItem(block.property.id, schemaGroup, { id }, index); + handleMovePropertyItem(block.propertyId, "default", { id }, index); }, - [block?.property?.id, block?.property?.items, handleMovePropertyItem], + [block?.propertyId, handleMovePropertyItem], ); // if there's no item add 1 button. // TODO: Should be added to block creationAPI for generic blocks that require at least 1 item useEffect(() => { - if (items.length === 0) { + if (!cameraButtons || cameraButtons.length === 0) { handleItemAdd(); return; } - }, [items.length, handleItemAdd]); + }, [cameraButtons, handleItemAdd, handleUpdate]); return ( = ({ block, isSelected, ...props }) => { const src = useMemo( - () => getFieldValue(block?.property?.items ?? [], "src") as ValueTypes["string"], - [block?.property?.items], + () => block?.property?.default?.src?.value as ValueTypes["string"], + [block?.property?.default?.src], ); return ( {src && } diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Markdown/index.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Markdown/index.tsx index 57cf052e20..028737f0e6 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Markdown/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Markdown/index.tsx @@ -2,7 +2,6 @@ import { useCallback, useMemo } from "react"; import { ValueTypes } from "@reearth/beta/utils/value"; -import { getFieldValue } from "../../../utils"; import { CommonProps as BlockProps } from "../../types"; import usePropertyValueUpdate from "../common/useActionPropertyApi"; import BlockWrapper from "../common/Wrapper"; @@ -13,29 +12,27 @@ export type Props = BlockProps; const MdBlock: React.FC = ({ block, isSelected, ...props }) => { const text = useMemo( - () => getFieldValue(block?.property?.items ?? [], "text") as ValueTypes["string"], - [block?.property?.items], + () => block?.property?.default?.text?.value as ValueTypes["string"], + [block?.property?.default?.text?.value], ); const { handlePropertyValueUpdate } = usePropertyValueUpdate(); const handleTextUpdate = useCallback( (text: string) => { - const schemaGroup = block?.property?.items?.find( - i => i.schemaGroup === "default", - )?.schemaGroup; - if (!block?.property?.id || !schemaGroup) return; - handlePropertyValueUpdate(schemaGroup, block?.property?.id, "text", "string")(text); + if (!block?.propertyId) return; + handlePropertyValueUpdate("default", block?.propertyId, "text", "string")(text); }, - [block?.property?.id, block?.property?.items, handlePropertyValueUpdate], + [block?.propertyId, handlePropertyValueUpdate], ); return ( diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Text/index.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Text/index.tsx index 35e2585834..bef03721f4 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Text/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Text/index.tsx @@ -2,7 +2,6 @@ import { useCallback, useMemo } from "react"; import { ValueTypes } from "@reearth/beta/utils/value"; -import { getFieldValue } from "../../../utils"; import { CommonProps as BlockProps } from "../../types"; import usePropertyValueUpdate from "../common/useActionPropertyApi"; import BlockWrapper from "../common/Wrapper"; @@ -16,29 +15,27 @@ export type Props = BlockProps; const TextBlock: React.FC = ({ block, isSelected, ...props }) => { const text = useMemo( - () => getFieldValue(block?.property?.items ?? [], "text") as ValueTypes["string"], - [block?.property?.items], + () => block?.property?.default?.text?.value as ValueTypes["string"], + [block?.property?.default?.text?.value], ); const { handlePropertyValueUpdate } = usePropertyValueUpdate(); const handleTextUpdate = useCallback( (text: string) => { - const schemaGroup = block?.property?.items?.find( - i => i.schemaGroup === "default", - )?.schemaGroup; - if (!block?.property?.id || !schemaGroup) return; - handlePropertyValueUpdate(schemaGroup, block?.property?.id, "text", "string")(text); + if (!block?.propertyId) return; + handlePropertyValueUpdate("default", block?.propertyId, "text", "string")(text); }, - [block?.property?.id, block?.property?.items, handlePropertyValueUpdate], + [block, handlePropertyValueUpdate], ); return ( diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Title/index.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Title/index.tsx index 3726e8deae..08d73926db 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Title/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Title/index.tsx @@ -5,7 +5,6 @@ import { ValueTypes } from "@reearth/beta/utils/value"; import { useT } from "@reearth/services/i18n"; import { styled } from "@reearth/services/theme"; -import { getFieldValue } from "../../../utils"; import { CommonProps as BlockProps } from "../../types"; import BlockWrapper from "../common/Wrapper"; @@ -13,26 +12,30 @@ export type Props = BlockProps; const TitleBlock: React.FC = ({ block, isSelected, ...props }) => { const t = useT(); - const text = useMemo( - () => getFieldValue(block?.property?.items ?? [], "title") as ValueTypes["string"], - [block?.property?.items], + + const property = useMemo(() => block?.property, [block?.property]); + + const title = useMemo( + () => property?.title?.title?.value as ValueTypes["string"], + [property?.title?.title?.value], ); const color = useMemo( - () => getFieldValue(block?.property?.items ?? [], "color") as ValueTypes["string"], - [block?.property?.items], + () => property?.title?.color?.value as ValueTypes["string"], + [property?.title?.color?.value], ); return ( - - {text ?? t("Untitled")} + <Title size="h2" hasText={!!title} color={color} customColor> + {title ?? t("Untitled")} ); diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Video/VideoPlayer.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Video/VideoPlayer.tsx index 7ef3cbaa92..3500e41fca 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Video/VideoPlayer.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Video/VideoPlayer.tsx @@ -1,6 +1,5 @@ import Player from "react-player"; -import Overlay from "@reearth/beta/components/Overlay"; import { styled } from "@reearth/services/theme"; type Props = { @@ -11,7 +10,7 @@ type Props = { const VideoPlayer: React.FC = ({ isSelected, src, inEditor }) => { return ( - + {inEditor && } ); @@ -23,3 +22,9 @@ const StyledWrapper = styled.div` position: relative; width: 100%; `; + +const Overlay = styled.div` + position: absolute; + width: 100%; + height: 100%; +`; diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/Video/index.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/Video/index.tsx index c877e29a4c..3064720af2 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/Video/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/Video/index.tsx @@ -3,20 +3,21 @@ import { useMemo } from "react"; import type { CommonProps as BlockProps } from "@reearth//beta/lib/core/StoryPanel/Block/types"; import BlockWrapper from "@reearth/beta/lib/core/StoryPanel/Block/builtin/common/Wrapper"; import VideoPlayer from "@reearth/beta/lib/core/StoryPanel/Block/builtin/Video/VideoPlayer"; -import { getFieldValue } from "@reearth/beta/lib/core/StoryPanel/utils"; import type { ValueTypes } from "@reearth/beta/utils/value"; const VideoBlock: React.FC = ({ block, isSelected, ...props }) => { const src = useMemo( - () => getFieldValue(block?.property?.items ?? [], "src") as ValueTypes["string"], - [block?.property?.items], + () => block?.property?.default?.src?.value as ValueTypes["string"], + [block?.property?.default?.src?.value], ); + return ( {src && } diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/common/ActionPanel/index.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/common/ActionPanel/index.tsx index c55d28a0ea..dece3e9d38 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/common/ActionPanel/index.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/common/ActionPanel/index.tsx @@ -1,7 +1,5 @@ import { Dispatch, SetStateAction } from "react"; -import type { Item } from "@reearth/services/api/propertyApi/utils"; - import ActionPanel, { type ActionPosition } from "../../../../ActionPanel"; import useHooks from "./hooks"; @@ -16,7 +14,7 @@ type Props = { showPadding?: boolean; editMode?: boolean; propertyId?: string; - panelSettings?: Item; + panelSettings?: any; dndEnabled?: boolean; position?: ActionPosition; setShowPadding: Dispatch>; diff --git a/web/src/beta/lib/core/StoryPanel/Block/builtin/common/Wrapper.tsx b/web/src/beta/lib/core/StoryPanel/Block/builtin/common/Wrapper.tsx index 9547c47b2e..360c493057 100644 --- a/web/src/beta/lib/core/StoryPanel/Block/builtin/common/Wrapper.tsx +++ b/web/src/beta/lib/core/StoryPanel/Block/builtin/common/Wrapper.tsx @@ -1,10 +1,9 @@ import { ReactNode, createContext } from "react"; -import FieldComponents from "@reearth/beta/components/fields/PropertyFields"; import { stopClickPropagation } from "@reearth/beta/utils/events"; -import { type Item } from "@reearth/services/api/propertyApi/utils"; import { styled } from "@reearth/services/theme"; +import { FieldComponent } from "../../../hooks/useFieldComponent"; import SelectableArea from "../../../SelectableArea"; import Template from "../../Template"; @@ -20,12 +19,13 @@ type Spacing = { }; type Props = { + name?: string | null; icon?: string; isSelected?: boolean; isEditable?: boolean; children?: ReactNode; propertyId?: string; - propertyItems?: Item[]; + property?: any; dndEnabled?: boolean; settingsEnabled?: boolean; onClick?: () => void; @@ -34,12 +34,13 @@ type Props = { }; const BlockWrapper: React.FC = ({ + name, icon, isSelected, isEditable, children, propertyId, - propertyItems, + property, dndEnabled = true, settingsEnabled = true, onClick, @@ -48,19 +49,23 @@ const BlockWrapper: React.FC = ({ }) => { const { title, + groupId, editMode, showSettings, defaultSettings, - padding, + panelSettings, setEditMode, handleEditModeToggle, handleSettingsToggle, handleBlockClick, } = useHooks({ + name, isSelected, - propertyItems, + property, + isEditable, onClick, }); + return ( = ({ propertyId={propertyId} dndEnabled={dndEnabled} showSettings={showSettings} - propertyItems={propertyItems} + panelSettings={panelSettings} editMode={editMode} isEditable={isEditable} setEditMode={setEditMode} @@ -78,12 +83,26 @@ const BlockWrapper: React.FC = ({ onSettingsToggle={handleSettingsToggle} onRemove={onRemove} onClickAway={onClickAway}> - + {children ??