diff --git a/web/src/beta/components/SidePanelSectionField/index.tsx b/web/src/beta/components/SidePanelSectionField/index.tsx index c38ee40bb5..3b4cf5382a 100644 --- a/web/src/beta/components/SidePanelSectionField/index.tsx +++ b/web/src/beta/components/SidePanelSectionField/index.tsx @@ -6,7 +6,7 @@ import Icon from "../Icon"; import Text from "../Text"; const SidePanelSectionField: React.FC<{ - title: string; + title?: string; children?: ReactNode; }> = ({ title, children }) => { const theme = useTheme(); @@ -14,12 +14,14 @@ const SidePanelSectionField: React.FC<{ return ( - setOpened(!opened)}> - - {title} - - - + {title && ( + setOpened(!opened)}> + + {title} + + + + )} {opened && children && {children}} ); @@ -45,7 +47,7 @@ const ArrowIcon = styled(Icon)<{ opened: boolean }>` `; const Content = styled.div` - padding: 0 8px 8px 8px; + padding: 8px; display: flex; flex-direction: column; gap: 4px; diff --git a/web/src/beta/features/Editor/Visualizer/CanvasArea/hooks.ts b/web/src/beta/features/Editor/Visualizer/CanvasArea/hooks.ts index 062fb617fc..865ef209df 100644 --- a/web/src/beta/features/Editor/Visualizer/CanvasArea/hooks.ts +++ b/web/src/beta/features/Editor/Visualizer/CanvasArea/hooks.ts @@ -1,3 +1,4 @@ +import { useReactiveVar } from "@apollo/client"; import { useMemo, useEffect, useCallback, useState } from "react"; import type { Alignment, Location } from "@reearth/beta/lib/core/Crust"; @@ -13,7 +14,7 @@ import { useSelectedBlock, useWidgetAlignEditorActivated, useZoomedLayerId, - useSelectedWidgetArea, + selectedWidgetAreaVar, } from "@reearth/services/state"; import { convertWidgets } from "./convert"; @@ -29,7 +30,7 @@ export default ({ sceneId, isBuilt }: { sceneId?: string; isBuilt?: boolean }) = const [camera, onCameraChange] = useCamera(); const [selected, select] = useSelected(); const [selectedBlock, selectBlock] = useSelectedBlock(); - const [selectedWidgetArea, selectWidgetArea] = useSelectedWidgetArea(); + const selectedWidgetArea = useReactiveVar(selectedWidgetAreaVar); const [widgetAlignEditorActivated] = useWidgetAlignEditorActivated(); const [zoomedLayerId, zoomToLayer] = useZoomedLayerId(); @@ -182,7 +183,10 @@ export default ({ sceneId, isBuilt }: { sceneId?: string; isBuilt?: boolean }) = const onWidgetAlignSystemUpdate = useCallback( async (location: Location, align: Alignment) => { - await useUpdateWidgetAlignSystem(location, align, sceneId); + await useUpdateWidgetAlignSystem( + { zone: location.zone, section: location.section, area: location.area, align }, + sceneId, + ); }, [sceneId, useUpdateWidgetAlignSystem], ); @@ -227,7 +231,7 @@ export default ({ sceneId, isBuilt }: { sceneId?: string; isBuilt?: boolean }) = onBlockRemove, onBlockInsert, onWidgetUpdate, - selectWidgetArea, + selectWidgetArea: selectedWidgetAreaVar, onWidgetAlignSystemUpdate, onIsCapturingChange, onCameraChange, diff --git a/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx b/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx index 07571c2a64..bad3a2f407 100644 --- a/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/Nav/index.tsx @@ -1,6 +1,9 @@ +import { useEffect } from "react"; + import Toggle from "@reearth/beta/components/properties/Toggle"; import SecondaryNav from "@reearth/beta/features/Editor/SecondaryNav"; import { useT } from "@reearth/services/i18n"; +import { selectedWidgetAreaVar } from "@reearth/services/state"; import { styled } from "@reearth/services/theme"; import Devices, { type Device } from "./Devices"; @@ -23,6 +26,13 @@ const Nav: React.FC = ({ onDeviceChange, }) => { const t = useT(); + + useEffect(() => { + if (!showWidgetEditor) { + selectedWidgetAreaVar(undefined); + } + }, [showWidgetEditor]); + return ( diff --git a/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx b/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx new file mode 100644 index 0000000000..662078420e --- /dev/null +++ b/web/src/beta/features/Editor/tabs/widgets/SidePanel/ContainerSettings/index.tsx @@ -0,0 +1,84 @@ +import TextInput from "@reearth/beta/components/properties/TextInput"; +import SidePanelSectionField from "@reearth/beta/components/SidePanelSectionField"; +import { WidgetAreaPadding, WidgetAreaState } from "@reearth/services/state"; + +type Props = { + widgetArea: WidgetAreaState; + onWidgetAreaStateChange: (widgetArea: WidgetAreaState) => void; +}; + +const ContainerSettings: React.FC = ({ widgetArea, onWidgetAreaStateChange }) => { + // TODO: This is dummy UI + return ( + + { + onWidgetAreaStateChange({ + ...widgetArea, + padding: { + ...(widgetArea.padding as WidgetAreaPadding), + top: Number(newVal) ?? 0, + }, + }); + }} + /> + { + onWidgetAreaStateChange({ + ...widgetArea, + padding: { + ...(widgetArea.padding as WidgetAreaPadding), + right: Number(newVal) ?? 0, + }, + }); + }} + /> + { + onWidgetAreaStateChange({ + ...widgetArea, + padding: { + ...(widgetArea.padding as WidgetAreaPadding), + bottom: Number(newVal) ?? 0, + }, + }); + }} + /> + { + onWidgetAreaStateChange({ + ...widgetArea, + padding: { + ...(widgetArea.padding as WidgetAreaPadding), + left: Number(newVal) ?? 0, + }, + }); + }} + /> + + { + onWidgetAreaStateChange({ + ...widgetArea, + gap: Number(newVal) ?? 0, + }); + }} + /> + + [Switch field] Align Center {!!widgetArea?.centered} + [Color field] Background Color {widgetArea?.background} + + ); +}; + +export default ContainerSettings; diff --git a/web/src/beta/features/Editor/tabs/widgets/SidePanel/hooks.ts b/web/src/beta/features/Editor/tabs/widgets/SidePanel/hooks.ts index 05ab04e532..1c2fb4b8d6 100644 --- a/web/src/beta/features/Editor/tabs/widgets/SidePanel/hooks.ts +++ b/web/src/beta/features/Editor/tabs/widgets/SidePanel/hooks.ts @@ -2,15 +2,25 @@ import { useReactiveVar } from "@apollo/client"; import { useCallback, useMemo } from "react"; import { useWidgetsFetcher } from "@reearth/services/api"; -import { selectedWidgetVar } from "@reearth/services/state"; +import { + selectedWidgetVar, + selectedWidgetAreaVar, + type WidgetAreaState, +} from "@reearth/services/state"; export default ({ sceneId }: { sceneId?: string }) => { - const { useInstallableWidgetsQuery, useInstalledWidgetsQuery, useAddWidget, useRemoveWidget } = - useWidgetsFetcher(); + const { + useInstallableWidgetsQuery, + useInstalledWidgetsQuery, + useAddWidget, + useRemoveWidget, + useUpdateWidgetAlignSystem, + } = useWidgetsFetcher(); const { installableWidgets } = useInstallableWidgetsQuery({ sceneId }); const { installedWidgets } = useInstalledWidgetsQuery({ sceneId }); const selectedWidget = useReactiveVar(selectedWidgetVar); + const selectedWidgetArea = useReactiveVar(selectedWidgetAreaVar); const propertyItems = useMemo( () => installedWidgets?.find(w => w.id === selectedWidget?.id)?.property.items, @@ -47,13 +57,26 @@ export default ({ sceneId }: { sceneId?: string }) => { [sceneId, useRemoveWidget], ); + const handleWidgetAreaStateChange = useCallback( + async (widgetAreaState?: WidgetAreaState) => { + if (!sceneId || !widgetAreaState) return; + const results = await useUpdateWidgetAlignSystem(widgetAreaState, sceneId); + if (results.status === "success") { + selectedWidgetAreaVar(widgetAreaState); + } + }, + [sceneId, useUpdateWidgetAlignSystem], + ); + return { selectedWidget, + selectedWidgetArea, propertyItems, installedWidgets, installableWidgets, handleWidgetAdd, handleWidgetRemove, handleWidgetSelection, + handleWidgetAreaStateChange, }; }; diff --git a/web/src/beta/features/Editor/tabs/widgets/SidePanel/index.tsx b/web/src/beta/features/Editor/tabs/widgets/SidePanel/index.tsx index d3e061a35d..688f4bec35 100644 --- a/web/src/beta/features/Editor/tabs/widgets/SidePanel/index.tsx +++ b/web/src/beta/features/Editor/tabs/widgets/SidePanel/index.tsx @@ -1,6 +1,7 @@ import SidePanelCommon from "@reearth/beta/features/Editor/SidePanel"; import { useT } from "@reearth/services/i18n"; +import ContainerSettings from "./ContainerSettings"; import useHooks from "./hooks"; import Manager, { ActionArea } from "./Manager"; import Settings from "./Settings"; @@ -15,12 +16,14 @@ const SidePanel: React.FC = ({ sceneId }) => { const { selectedWidget, + selectedWidgetArea, propertyItems, installedWidgets, installableWidgets, handleWidgetAdd, handleWidgetRemove, handleWidgetSelection, + handleWidgetAreaStateChange, } = useHooks({ sceneId }); return ( @@ -51,6 +54,17 @@ const SidePanel: React.FC = ({ sceneId }) => { ), }, + { + id: "containerSettings", + title: t("Container Settings"), + hide: !selectedWidgetArea, + children: selectedWidgetArea && sceneId && ( + + ), + }, ]} /> ); diff --git a/web/src/beta/lib/core/Crust/Widgets/WidgetAlignSystem/Area.tsx b/web/src/beta/lib/core/Crust/Widgets/WidgetAlignSystem/Area.tsx index 4c3c481809..4a9f8ea026 100644 --- a/web/src/beta/lib/core/Crust/Widgets/WidgetAlignSystem/Area.tsx +++ b/web/src/beta/lib/core/Crust/Widgets/WidgetAlignSystem/Area.tsx @@ -120,7 +120,7 @@ export default function Area({ editorStyle={{ flexWrap: "wrap", padding: `${padding?.top}px ${padding?.right}px ${padding?.bottom}px ${padding?.left}px`, - background: backgroundColor + backgroundColor: backgroundColor ? backgroundColor : area === "middle" ? theme.placeHolder.main_2 diff --git a/web/src/services/api/widgetsApi/index.ts b/web/src/services/api/widgetsApi/index.ts index baf8531e03..ba489e5894 100644 --- a/web/src/services/api/widgetsApi/index.ts +++ b/web/src/services/api/widgetsApi/index.ts @@ -16,7 +16,7 @@ import { UPDATE_WIDGET_ALIGN_SYSTEM, } from "@reearth/services/gql/queries/widget"; import { useT } from "@reearth/services/i18n"; -import { useNotification } from "@reearth/services/state"; +import { WidgetAreaState, useNotification } from "@reearth/services/state"; import { SceneQueryProps } from "../sceneApi"; import { MutationReturn } from "../types"; @@ -196,7 +196,7 @@ export default () => { }); const useUpdateWidgetAlignSystem = useCallback( - async (location: WidgetLocation, align: WidgetAlignment, sceneId?: string) => { + async (widgetAreaState: WidgetAreaState, sceneId?: string) => { if (!sceneId) { console.log( "GraphQL: Failed to update the widget align system because there is no sceneId provided", @@ -211,11 +211,15 @@ export default () => { variables: { sceneId, location: { - zone: location.zone.toUpperCase() as WidgetZoneType, - section: location.section.toUpperCase() as WidgetSectionType, - area: location.area.toUpperCase() as WidgetAreaType, + zone: widgetAreaState.zone.toUpperCase() as WidgetZoneType, + section: widgetAreaState.section.toUpperCase() as WidgetSectionType, + area: widgetAreaState.area.toUpperCase() as WidgetAreaType, }, - align: align?.toUpperCase() as WidgetAreaAlign, + align: widgetAreaState.align?.toUpperCase() as WidgetAreaAlign, + background: widgetAreaState.background, + padding: widgetAreaState.padding, + centered: widgetAreaState.centered, + gap: widgetAreaState.gap, }, }); diff --git a/web/src/services/i18n/translations/en.yml b/web/src/services/i18n/translations/en.yml index 8d95ce6c80..7314e6378b 100644 --- a/web/src/services/i18n/translations/en.yml +++ b/web/src/services/i18n/translations/en.yml @@ -36,6 +36,7 @@ Page: Page Align System: Align System Widget Manager: Widget Manager Inspector: Inspector +Container Settings: Container Settings Angle: Angle Narrow: Narrow Wide: Wide diff --git a/web/src/services/i18n/translations/ja.yml b/web/src/services/i18n/translations/ja.yml index 11c136d59c..19f6c65cdf 100644 --- a/web/src/services/i18n/translations/ja.yml +++ b/web/src/services/i18n/translations/ja.yml @@ -32,6 +32,7 @@ Page: ページ Align System: アラインシステム Widget Manager: ウィジェット管理 Inspector: インスペクター +Container Settings: Container Settings Angle: 角度 Narrow: 狭い Wide: 広い diff --git a/web/src/services/state/index.ts b/web/src/services/state/index.ts index 1cd29584b6..a108efad48 100644 --- a/web/src/services/state/index.ts +++ b/web/src/services/state/index.ts @@ -1,5 +1,7 @@ import { makeVar } from "@apollo/client"; +import type { WidgetAreaType } from "@reearth/beta/lib/core/Crust"; + export type SelectedWidget = { id: string; pluginId: string; @@ -9,4 +11,6 @@ export type SelectedWidget = { export const selectedWidgetVar = makeVar(undefined); +export const selectedWidgetAreaVar = makeVar(undefined); + export * from "./jotai";