({
+ key,
+ label: title,
+ }))}
onChange={handlePropertyValueUpdate(groupId, propertyId, fieldId, field?.type)}
/>
) : field?.ui === "buttons" ? (
@@ -115,6 +146,13 @@ export const FieldComponent = ({
onChange={handlePropertyValueUpdate(groupId, propertyId, fieldId, field?.type)}
/>
)
+ ) : field?.type === "timeline" ? (
+
) : (
{t("Unsupported field type")}
);
diff --git a/web/src/beta/lib/core/StoryPanel/hooks/useTimelineBlock.ts b/web/src/beta/lib/core/StoryPanel/hooks/useTimelineBlock.ts
new file mode 100644
index 0000000000..bf5cb149c1
--- /dev/null
+++ b/web/src/beta/lib/core/StoryPanel/hooks/useTimelineBlock.ts
@@ -0,0 +1,161 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+
+import { useVisualizer } from "@reearth/beta/lib/core/Visualizer";
+
+import { TickEventCallback, TimelineCommitter } from "../../Map/useTimelineManager";
+import { TimelineValues } from "../Block/builtin/Timeline";
+import { convertOptionToSeconds, formatDateToSting } from "../utils";
+
+export const getNewDate = (d?: Date) => d ?? new Date();
+
+const calculateEndTime = (date: Date) => {
+ date.setHours(23, 59, 59, 999);
+ return date.getTime();
+};
+
+const calculateMidTime = (startTime: number, stopTime: number) => {
+ return (startTime + stopTime) / 2;
+};
+
+const timeRange = (startTime?: number, stopTime?: number) => {
+ // To avoid out of range error in Cesium, we need to turn back a hour.
+ const now = Date.now() - 3600000;
+ return {
+ start: startTime || now,
+ end: stopTime || stopTime || calculateEndTime(new Date()),
+ mid: calculateMidTime(startTime || now, stopTime || calculateEndTime(new Date())),
+ };
+};
+
+export default (timelineValues?: TimelineValues) => {
+ const visualizerContext = useVisualizer();
+
+ const playSpeedOptions = useMemo(() => {
+ const speedOpt = ["1min/sec", "0.1hr/sec", "0.5hr/sec", "1hr/sec"];
+ return convertOptionToSeconds(speedOpt);
+ }, []);
+
+ const [speed, setSpeed] = useState(playSpeedOptions[0].seconds);
+
+ const [currentTime, setCurrentTime] = useState(
+ getNewDate(visualizerContext?.current?.timeline?.current?.timeline?.current).getTime(),
+ );
+
+ const range = useMemo(() => {
+ if (timelineValues) {
+ const startTime = getNewDate(new Date(timelineValues?.startTime.substring(0, 19))).getTime();
+ const endTime = getNewDate(new Date(timelineValues?.endTime.substring(0, 19))).getTime();
+ return timeRange(startTime, endTime);
+ } else {
+ return timeRange(
+ visualizerContext?.current?.timeline?.current?.timeline?.start?.getTime(),
+ visualizerContext?.current?.timeline?.current?.timeline?.stop?.getTime(),
+ );
+ }
+ }, [timelineValues, visualizerContext]);
+
+ const onPause = useCallback(
+ (committerId?: string) => {
+ return visualizerContext.current?.timeline?.current?.commit({
+ cmd: "PAUSE",
+ committer: { source: "storyTimelineBlock", id: committerId },
+ });
+ },
+ [visualizerContext],
+ );
+
+ const onPlay = useCallback(
+ (committer?: TimelineCommitter) => {
+ return visualizerContext.current?.timeline?.current?.commit({
+ cmd: "PLAY",
+ committer: { source: "storyTimelineBlock", id: committer?.id },
+ });
+ },
+ [visualizerContext],
+ );
+
+ const onTimeChange = useCallback(
+ (time: Date, committerId?: string) => {
+ if (!range) return;
+ return visualizerContext.current?.timeline?.current?.commit({
+ cmd: "SET_TIME",
+ payload: {
+ start: formatDateToSting(range.start),
+ current: time,
+ stop: formatDateToSting(range.end),
+ },
+ committer: { source: "storyTimelineBlock", id: committerId },
+ });
+ },
+ [range, visualizerContext],
+ );
+
+ const onSpeedChange = useCallback(
+ (speed: number, committerId?: string) => {
+ return visualizerContext.current?.timeline?.current?.commit({
+ cmd: "SET_OPTIONS",
+ payload: {
+ multiplier: speed,
+ stepType: "rate",
+ },
+ committer: { source: "storyTimelineBlock", id: committerId },
+ });
+ },
+ [visualizerContext],
+ );
+
+ const onTick = useCallback(
+ (cb: TickEventCallback) => visualizerContext?.current?.timeline?.current?.onTick(cb),
+ [visualizerContext],
+ );
+ const removeTickEventListener = useCallback(
+ (cb: TickEventCallback) => visualizerContext?.current?.timeline?.current?.offTick(cb),
+ [visualizerContext],
+ );
+
+ const removeOnCommitEventListener = useCallback(
+ (cb: (committer: TimelineCommitter) => void) =>
+ visualizerContext?.current?.timeline?.current?.offCommit(cb),
+ [visualizerContext],
+ );
+
+ const onCommit = useCallback(
+ (cb: (committer: TimelineCommitter) => void) =>
+ visualizerContext?.current?.timeline?.current?.onCommit(cb),
+ [visualizerContext],
+ );
+ const handleOnSpeedChange = useCallback(
+ (speed: number, committerId?: string) => {
+ onSpeedChange?.(speed, committerId);
+ setSpeed(speed);
+ },
+ [onSpeedChange],
+ );
+
+ useEffect(() => {
+ if (timelineValues) {
+ const t = getNewDate(new Date(timelineValues?.currentTime.substring(0, 19))).getTime();
+ return setCurrentTime(t);
+ } else {
+ return setCurrentTime(
+ getNewDate(visualizerContext?.current?.timeline?.current?.timeline?.current).getTime(),
+ );
+ }
+ }, [timelineValues, visualizerContext]);
+
+ return {
+ currentTime,
+ range,
+ playSpeedOptions,
+ speed,
+ onPlay,
+ onSpeedChange: handleOnSpeedChange,
+ onPause,
+ onTimeChange,
+ onTick,
+ removeTickEventListener,
+ onCommit,
+ removeOnCommitEventListener,
+ setCurrentTime,
+ };
+};
diff --git a/web/src/beta/lib/core/StoryPanel/index.tsx b/web/src/beta/lib/core/StoryPanel/index.tsx
index b779db8294..a1a7ba14d3 100644
--- a/web/src/beta/lib/core/StoryPanel/index.tsx
+++ b/web/src/beta/lib/core/StoryPanel/index.tsx
@@ -24,12 +24,14 @@ export type StoryPanelProps = {
selectedStory?: Story;
isEditable?: boolean;
installableBlocks?: InstallableStoryBlock[];
+ onCurrentPageChange?: (id: string, disableScrollIntoView?: boolean) => void;
onBlockCreate?: (
pageId?: string | undefined,
extensionId?: string | undefined,
pluginId?: string | undefined,
index?: number | undefined,
) => Promise;
+ onBlockMove?: (id: string, targetId: number, blockId: string) => void;
onBlockDelete?: (pageId?: string | undefined, blockId?: string | undefined) => Promise;
onPropertyUpdate?: (
propertyId?: string,
@@ -39,8 +41,18 @@ export type StoryPanelProps = {
vt?: ValueType,
v?: ValueTypes[ValueType],
) => Promise;
- onCurrentPageChange?: (id: string, disableScrollIntoView?: boolean) => void;
- onStoryBlockMove?: (id: string, targetId: number, blockId: string) => void;
+ onPropertyItemAdd?: (propertyId?: string, schemaGroupId?: string) => Promise;
+ onPropertyItemMove?: (
+ propertyId?: string,
+ schemaGroupId?: string,
+ itemId?: string,
+ index?: number,
+ ) => Promise;
+ onPropertyItemDelete?: (
+ propertyId?: string,
+ schemaGroupId?: string,
+ itemId?: string,
+ ) => Promise;
};
export const StoryPanel = memo(
@@ -50,11 +62,14 @@ export const StoryPanel = memo(
selectedStory,
isEditable,
installableBlocks,
+ onCurrentPageChange,
onBlockCreate,
+ onBlockMove,
onBlockDelete,
onPropertyUpdate,
- onCurrentPageChange,
- onStoryBlockMove,
+ onPropertyItemAdd,
+ onPropertyItemMove,
+ onPropertyItemDelete,
},
ref: Ref,
) => {
@@ -77,9 +92,8 @@ export const StoryPanel = memo(
},
ref,
);
-
return (
-
+
{!!pageInfo && (
);
@@ -114,8 +131,8 @@ export const StoryPanel = memo(
export default StoryPanel;
-const PanelWrapper = styled.div`
+const PanelWrapper = styled.div<{ bgColor?: string }>`
flex: 0 0 ${STORY_PANEL_WIDTH}px;
- background: #f1f1f1;
+ background: ${({ bgColor }) => bgColor};
color: ${({ theme }) => theme.content.weak};
`;
diff --git a/web/src/beta/lib/core/StoryPanel/types.ts b/web/src/beta/lib/core/StoryPanel/types.ts
index 4f27526332..a8ad315f36 100644
--- a/web/src/beta/lib/core/StoryPanel/types.ts
+++ b/web/src/beta/lib/core/StoryPanel/types.ts
@@ -3,6 +3,7 @@ export type Position = "left" | "right";
export type Story = {
id: string;
title?: string;
+ bgColor?: string;
position: Position;
pages: StoryPage[];
};
@@ -32,4 +33,5 @@ export type Field = {
title?: string;
description?: string;
value?: V;
+ choices?: string[];
};
diff --git a/web/src/beta/lib/core/StoryPanel/utils.ts b/web/src/beta/lib/core/StoryPanel/utils.ts
index 3353ee7d40..a2021a30f3 100644
--- a/web/src/beta/lib/core/StoryPanel/utils.ts
+++ b/web/src/beta/lib/core/StoryPanel/utils.ts
@@ -3,6 +3,8 @@ import type { Item } from "@reearth/services/api/propertyApi/utils";
import type { Spacing } from "../mantle";
+import { MIN_STORY_PAGE_PADDING_IN_EDITOR } from "./constants";
+
export const getFieldValue = (items: Item[], fieldId: string, fieldGroup?: string) => {
const d = items.find(i => i.schemaGroup === (fieldGroup ?? "default")) ?? items[0];
const isList = d && "items" in d;
@@ -24,13 +26,16 @@ export const calculatePaddingValue = (
editorMode?: boolean,
) => {
const calculateValue = (position: keyof Spacing, v?: number): { [key: string]: number } => {
- if (!v) {
+ if (v === undefined) {
return {
[position]: editorMode ? defaultValue[position] : 0,
};
}
return {
- [position]: editorMode && v < defaultValue[position] ? defaultValue[position] : v,
+ [position]:
+ editorMode && v < MIN_STORY_PAGE_PADDING_IN_EDITOR[position]
+ ? MIN_STORY_PAGE_PADDING_IN_EDITOR[position]
+ : v,
};
};
@@ -43,3 +48,120 @@ export const calculatePaddingValue = (
)
: defaultValue;
};
+
+const MONTH_LABEL_LIST = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+];
+
+const formatTimezone = (time: number) => {
+ const d = new Date(time);
+ const timezoneOffset = d.getTimezoneOffset();
+ const timezoneSign = timezoneOffset >= 0 ? "-" : "+";
+ const timezoneHours = Math.floor(Math.abs(timezoneOffset) / 60);
+ const timezoneMinutes = Math.abs(timezoneOffset) % 60;
+ const timezone = `${timezoneSign}${timezoneHours}:${timezoneMinutes.toString().padStart(2, "0")}`;
+ return timezone;
+};
+export const formatDateForTimeline = (time: number, options: { detail?: boolean } = {}) => {
+ const d = new Date(time);
+
+ const year = d.getFullYear();
+ const month = MONTH_LABEL_LIST[d.getMonth()];
+ const date = `${d.getDate()}`.padStart(2, "0");
+ const hour = `${d.getHours()}`.padStart(2, "0");
+ if (!options.detail) {
+ const timezone = formatTimezone(time);
+ return `${year} ${month} ${date} ${hour}:00:00.00 ${timezone}`;
+ }
+ const minutes = `${d.getMinutes()}`.padStart(2, "0");
+ const seconds = `${d.getSeconds()}`.padStart(2, "0");
+ const timezone = formatTimezone(time);
+
+ return `${year} ${month} ${date} ${hour}:${minutes}:${seconds}${timezone}`;
+};
+
+export const formatDateForSliderTimeline = (time: number, options: { detail?: boolean } = {}) => {
+ const d = new Date(time);
+
+ const month = MONTH_LABEL_LIST[d.getMonth()];
+ const date = `${d.getDate()}`.padStart(2, "0");
+ const hour = `${d.getHours()}`.padStart(2, "0");
+ if (!options.detail) {
+ return `${month} ${date} ${hour}`;
+ }
+ const minutes = `${d.getMinutes()}`.padStart(2, "0");
+ return ` ${month} ${date} ${hour}:${minutes}`;
+};
+
+export const formatDateToSting = (d: number) => {
+ const date = new Date(d);
+ return date.toISOString();
+};
+
+const timeStringToSeconds = (timeString: string) => {
+ const parts = timeString.split("/");
+ const valueUnit = parts[0].trim();
+ const value = parseFloat(valueUnit);
+ const unit = valueUnit.substr(value.toString().length).trim().toLowerCase();
+
+ switch (unit) {
+ case "sec":
+ case "secs":
+ return value;
+ case "min":
+ case "mins":
+ return value * 60;
+ case "hr":
+ case "hrs":
+ return value * 3600;
+ default:
+ return NaN;
+ }
+};
+
+export const convertOptionToSeconds = (data: string[]) => {
+ const objectsArray = [];
+
+ for (const timeString of data) {
+ const seconds = timeStringToSeconds(timeString);
+ if (!isNaN(seconds)) {
+ objectsArray.push({ timeString, seconds });
+ }
+ }
+
+ return objectsArray;
+};
+
+export const formatRangeDateAndTime = (data: string) => {
+ const lastIdx = data.lastIndexOf(" ");
+ const date = data.slice(0, lastIdx);
+ const time = data.slice(lastIdx + 1);
+ return {
+ date,
+ time,
+ };
+};
+
+export const convertPositionToTime = (e: MouseEvent, start: number, end: number) => {
+ const curTar = e.currentTarget as HTMLElement;
+ const width = curTar.scrollWidth - 32;
+ const rect = curTar.getBoundingClientRect();
+ const scrollX = curTar.scrollLeft;
+ const clientX = e.clientX - rect.x;
+ const posX = clientX + scrollX;
+ const percent = posX / width;
+ const rangeDiff = end - start;
+ const sec = rangeDiff * percent;
+ return Math.min(Math.max(start, start + sec), end);
+};
diff --git a/web/src/beta/lib/core/Visualizer/hooks.ts b/web/src/beta/lib/core/Visualizer/hooks.ts
index 9812f1024c..5b7be0de12 100644
--- a/web/src/beta/lib/core/Visualizer/hooks.ts
+++ b/web/src/beta/lib/core/Visualizer/hooks.ts
@@ -52,8 +52,8 @@ export default function useHooks(
ownBuiltinWidgets?: (keyof BuiltinWidgets)[];
onLayerSelect?: (
layerId: string | undefined,
- featureId: string | undefined,
layer: (() => Promise) | undefined,
+ feature: ComputedFeature | undefined,
reason: LayerSelectionReason | undefined,
) => void;
onBlockSelect?: (blockId?: string) => void;
@@ -117,17 +117,17 @@ export default function useHooks(
if (selectedLayer.layerId === layerId && selectedLayer.featureId === featureId) return;
const computedLayer = await layer?.();
+ const computedFeature =
+ layerId && featureId
+ ? mapRef.current?.engine.findComputedFeatureById?.(layerId, featureId) ?? info?.feature
+ : undefined;
selectFeature(
layerId && featureId
? mapRef.current?.engine.findFeatureById?.(layerId, featureId)
: undefined,
);
- selectComputedFeature(
- layerId && featureId
- ? mapRef.current?.engine.findComputedFeatureById?.(layerId, featureId) ?? info?.feature
- : undefined,
- );
+ selectComputedFeature(computedFeature);
selectLayer(l =>
l.layerId === layerId && l.featureId === featureId
@@ -135,7 +135,7 @@ export default function useHooks(
: { layerId, featureId, layer: computedLayer, reason },
);
- onLayerSelect?.(layerId, featureId, layer, reason);
+ onLayerSelect?.(layerId, layer, computedFeature, reason);
},
[selectedLayer, onLayerSelect],
);
diff --git a/web/src/beta/lib/core/Visualizer/index.tsx b/web/src/beta/lib/core/Visualizer/index.tsx
index 920d68ddc8..0e17cc78bd 100644
--- a/web/src/beta/lib/core/Visualizer/index.tsx
+++ b/web/src/beta/lib/core/Visualizer/index.tsx
@@ -22,7 +22,7 @@ import Crust, {
BuiltinWidgets,
InteractionModeType,
} from "../Crust";
-import { Tag } from "../mantle";
+import { ComputedFeature, Tag } from "../mantle";
import Map, {
type ValueTypes,
type ValueType,
@@ -69,7 +69,7 @@ export type Props = {
floatingWidgets?: InternalWidget[];
sceneProperty?: SceneProperty;
layers?: Layer[];
- clusters?: Cluster[];
+ clusters?: Cluster[]; // TODO: remove completely from beta core
camera?: Camera;
storyPanelPosition?: Position;
interactionMode?: InteractionModeType;
@@ -77,7 +77,7 @@ export type Props = {
style?: CSSProperties;
small?: boolean;
ready?: boolean;
- tags?: Tag[];
+ tags?: Tag[]; // TODO: remove completely from beta core
selectedBlockId?: string;
useExperimentalSandbox?: boolean;
selectedWidgetArea?: WidgetAreaType;
@@ -87,8 +87,8 @@ export type Props = {
onLayerDrop?: (layerId: string, propertyKey: string, position: LatLng | undefined) => void;
onLayerSelect?: (
layerId: string | undefined,
- featureId: string | undefined,
layer: (() => Promise) | undefined,
+ feature: ComputedFeature | undefined,
reason: LayerSelectionReason | undefined,
) => void;
onWidgetLayoutUpdate?: (
diff --git a/web/src/beta/lib/core/engines/Cesium/Feature/Marker/index.tsx b/web/src/beta/lib/core/engines/Cesium/Feature/Marker/index.tsx
index 094e37cf23..95d865749a 100644
--- a/web/src/beta/lib/core/engines/Cesium/Feature/Marker/index.tsx
+++ b/web/src/beta/lib/core/engines/Cesium/Feature/Marker/index.tsx
@@ -1,11 +1,12 @@
import { Cartesian3, Color, HorizontalOrigin, VerticalOrigin, Cartesian2 } from "cesium";
-import { useMemo } from "react";
+import { useEffect, useMemo } from "react";
import { BillboardGraphics, PointGraphics, LabelGraphics, PolylineGraphics } from "resium";
import { toCSSFont } from "@reearth/beta/utils/value";
import type { MarkerAppearance } from "../../..";
import { useIcon, ho, vo, heightReference, toColor } from "../../common";
+import { useContext } from "../context";
import {
EntityExt,
toDistanceDisplayCondition,
@@ -37,6 +38,8 @@ export default function Marker({ property, id, isVisible, geometry, layer, featu
[geometry?.coordinates, geometry?.type, property?.height, property?.location],
);
+ const { requestRender } = useContext();
+
const {
show = true,
extrude,
@@ -134,6 +137,10 @@ export default function Marker({ property, id, isVisible, geometry, layer, featu
[property?.near, property?.far],
);
+ useEffect(() => {
+ requestRender?.();
+ });
+
return !pos || !isVisible || !show ? null : (
<>
{extrudePoints && (
diff --git a/web/src/beta/lib/core/engines/Cesium/Feature/Model/index.tsx b/web/src/beta/lib/core/engines/Cesium/Feature/Model/index.tsx
index e565c899a8..29b20ebad7 100644
--- a/web/src/beta/lib/core/engines/Cesium/Feature/Model/index.tsx
+++ b/web/src/beta/lib/core/engines/Cesium/Feature/Model/index.tsx
@@ -176,6 +176,10 @@ export default function Model({
}
}, [imageBasedLighting]);
+ useEffect(() => {
+ requestRender?.();
+ });
+
// if data type is gltf, layer should be rendered. Otherwise only features should be rendererd.
return (isGltfData ? feature : !feature) || !isVisible || !show || !actualUrl ? null : (
{
+ requestRender?.();
+ });
+
return !isVisible || !coordiantes || !show ? null : (
<>
{
+ requestRender?.();
+ });
+
return !isVisible || !coordinates || !show ? null : (
prevDeps[0] === nextDeps[0] &&
- prevDeps[1] === nextDeps[1] &&
+ isEqual(prevDeps[1], nextDeps[1]) &&
isEqual(prevDeps[2], nextDeps[2]) &&
prevDeps[3] === nextDeps[3] &&
- prevDeps[4] === nextDeps[4],
+ prevDeps[4] === nextDeps[4] &&
+ prevDeps[5] === nextDeps[5],
);
const handleUnmount = useCallback(() => {
@@ -460,7 +463,7 @@ export default ({
sphericalHarmonicCoefficients,
globeShadowDarkness: property?.atmosphere?.globeShadowDarkness,
globeImageBasedLighting: property?.atmosphere?.globeImageBasedLighting,
- enableLighting: property?.atmosphere?.enable_lighting,
+ enableLighting: property?.atmosphere?.enable_lighting ?? property?.globeLighting?.globeLighting,
hasVertexNormals: property?.terrain?.terrain && property.terrain.terrainNormal,
});
diff --git a/web/src/beta/lib/core/engines/Cesium/index.tsx b/web/src/beta/lib/core/engines/Cesium/index.tsx
index f213bd1856..7e3a87422e 100644
--- a/web/src/beta/lib/core/engines/Cesium/index.tsx
+++ b/web/src/beta/lib/core/engines/Cesium/index.tsx
@@ -122,7 +122,7 @@ const Cesium: React.ForwardRefRenderFunction = (
cursor: isLayerDragging ? "grab" : undefined,
...style,
}}
- shadows={!!property?.atmosphere?.shadows}
+ shadows={!!(property?.atmosphere?.shadows ?? property?.globeShadow?.globeShadow)}
onClick={handleClick}
onDoubleClick={mouseEventHandles.doubleclick}
onMouseDown={mouseEventHandles.mousedown}
@@ -153,7 +153,9 @@ const Cesium: React.ForwardRefRenderFunction = (
? property.cameraLimiter?.cameraLimitterTargetArea?.height ?? Number.POSITIVE_INFINITY
: Number.POSITIVE_INFINITY
}
- enableCollisionDetection={!property?.default?.allowEnterGround}
+ enableCollisionDetection={
+ !(property?.default?.allowEnterGround ?? property?.camera?.allowEnterGround)
+ }
/>
= (
useDepthPicking={false}
debugShowFramesPerSecond={!!property?.render?.debugFramePerSecond}
/>
-
+
-
-
+
+
diff --git a/web/src/beta/lib/lexical/RichTextEditor/index.tsx b/web/src/beta/lib/lexical/RichTextEditor/index.tsx
index 32a1e9b617..e4bf95ed24 100644
--- a/web/src/beta/lib/lexical/RichTextEditor/index.tsx
+++ b/web/src/beta/lib/lexical/RichTextEditor/index.tsx
@@ -30,12 +30,7 @@ type Props = {
onChange?: (text: EditorStateJSONString) => void;
};
-const RichTextEditor: React.FC = ({
- editMode = true,
- text,
- scrollableContainerId,
- onChange,
-}) => {
+const RichTextEditor: React.FC = ({ editMode, text, scrollableContainerId, onChange }) => {
const t = useT();
const editorStateJSONStringRef = useRef();
@@ -98,7 +93,7 @@ const RichTextEditor: React.FC = ({
-
+
{editorRef.current && (
= ({ sceneId, projectId, workspaceId, renderI
);
const currentWorkspaceId = useMemo(
- () => workspaceId ?? scene?.teamId,
- [workspaceId, scene?.teamId],
+ () => workspaceId ?? scene?.workspaceId,
+ [workspaceId, scene?.workspaceId],
);
const { loading: loadingProject } = useProjectQuery(currentProjectId);
diff --git a/web/src/beta/utils/use-double-click.ts b/web/src/beta/utils/use-double-click.ts
new file mode 100644
index 0000000000..636ab85fa9
--- /dev/null
+++ b/web/src/beta/utils/use-double-click.ts
@@ -0,0 +1,33 @@
+import { useCallback, useRef } from "react";
+
+const useDoubleClick = (
+ onClick: (() => void) | undefined,
+ onDoubleClick: (() => void) | undefined,
+ delay = 200,
+): [() => void, () => void] => {
+ const timerRef = useRef(null);
+
+ const singleClickHandler = useCallback(() => {
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ timerRef.current = null;
+ } else if (onClick) {
+ timerRef.current = setTimeout(() => {
+ onClick();
+ timerRef.current = null;
+ }, delay);
+ }
+ }, [onClick, delay]);
+
+ const doubleClickHandler = useCallback(() => {
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ timerRef.current = null;
+ }
+ onDoubleClick?.();
+ }, [onDoubleClick]);
+
+ return [singleClickHandler, doubleClickHandler];
+};
+
+export default useDoubleClick;
diff --git a/web/src/classic/components/atoms/Notification/index.stories.tsx b/web/src/classic/components/atoms/Notification/index.stories.tsx
deleted file mode 100644
index aa3be1f938..0000000000
--- a/web/src/classic/components/atoms/Notification/index.stories.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Meta } from "@storybook/react";
-
-import Notification from ".";
-
-export default {
- title: "classic/atoms/NotificationBanner",
- component: Notification,
-} as Meta;
-
-export const Success = () => (
-
-);
-export const Error = () => (
-
-);
-export const Warning = () => (
-
-);
-export const Info = () => (
-
-);
diff --git a/web/src/classic/components/atoms/Notification/index.test.tsx b/web/src/classic/components/atoms/Notification/index.test.tsx
deleted file mode 100644
index a3449fb54a..0000000000
--- a/web/src/classic/components/atoms/Notification/index.test.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { expect, test } from "vitest";
-
-import { render, screen } from "@reearth/test/utils";
-
-import NotificationBanner, { Notification } from ".";
-
-const sampleNotification: Notification = {
- type: "info",
- heading: "Notice",
- text: "This is a notification for something super cool.",
-};
-
-test("should display notification heading", () => {
- render();
- expect(screen.getByText(/Notice/)).toBeInTheDocument();
- expect(screen.getByText(/This is a notification for something super cool./)).toBeInTheDocument();
-});
-
-test("should not display anything", () => {
- render();
- expect(screen.getByText(/This is a notification for something super cool./)).not.toBeVisible();
-});
diff --git a/web/src/classic/components/atoms/Notification/index.tsx b/web/src/classic/components/atoms/Notification/index.tsx
deleted file mode 100644
index bf916d6746..0000000000
--- a/web/src/classic/components/atoms/Notification/index.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import React from "react";
-
-import Flex from "@reearth/classic/components/atoms/Flex";
-import Icon from "@reearth/classic/components/atoms/Icon";
-import Text from "@reearth/classic/components/atoms/Text";
-import { metrics } from "@reearth/classic/theme";
-import { styled, useTheme } from "@reearth/services/theme";
-
-export type NotificationType = "error" | "warning" | "info" | "success";
-export type Notification = {
- type: NotificationType;
- heading?: string;
- text: string;
-};
-
-export type Props = {
- visible?: boolean;
- notification?: Notification;
- setModal?: (show: boolean) => void;
- resetNotification?: () => void;
-};
-
-const NotificationBanner: React.FC = ({
- visible,
- notification,
- setModal,
- resetNotification,
-}) => {
- const theme = useTheme();
-
- return (
-
-
-
- {notification?.heading}
-
- {
- setModal?.(false);
- resetNotification?.();
- }}
- />
-
-
- {notification?.text}
-
-
- );
-};
-
-const StyledNotificationBanner = styled(Flex)<{
- type?: NotificationType;
- visible?: boolean;
-}>`
- position: absolute;
- top: ${metrics.headerHeight}px;
- right: 0;
- width: 312px;
- padding: 8px 12px;
- background-color: ${({ type, theme }) =>
- type === "error"
- ? theme.classic.notification.errorBg
- : type === "warning"
- ? theme.classic.notification.warningBg
- : type === "success"
- ? theme.classic.notification.successBg
- : theme.classic.notification.infoBg};
- color: ${({ theme }) => theme.classic.notification.text};
- z-index: ${({ theme, visible }) => (visible ? theme.classic.zIndexes.notificationBar : 0)};
- opacity: ${({ visible }) => (visible ? "1" : "0")};
- transition: all 0.5s;
- pointer-events: ${({ visible }) => (visible ? "auto" : "none")};
-`;
-
-const HeadingArea = styled(Flex)`
- width: 100%;
-`;
-
-const CloseBtn = styled(Icon)`
- cursor: pointer;
-`;
-
-export default NotificationBanner;
diff --git a/web/src/classic/components/organisms/Authentication/RootPage/hooks.ts b/web/src/classic/components/organisms/Authentication/RootPage/hooks.ts
index efd2494541..d41e9966ac 100644
--- a/web/src/classic/components/organisms/Authentication/RootPage/hooks.ts
+++ b/web/src/classic/components/organisms/Authentication/RootPage/hooks.ts
@@ -3,9 +3,9 @@ import { useCallback, useEffect, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useGetTeamsQuery } from "@reearth/classic/gql";
-import { useWorkspace, useNotification, useUserId } from "@reearth/classic/state";
import { useAuth, useCleanUrl } from "@reearth/services/auth";
import { useT } from "@reearth/services/i18n";
+import { useWorkspace, useNotification, useUserId } from "@reearth/services/state";
export type Mode = "layer" | "widget";
diff --git a/web/src/classic/components/organisms/Common/AssetContainer/hooks.ts b/web/src/classic/components/organisms/Common/AssetContainer/hooks.ts
index eb29f3c8ca..90977ec7e9 100644
--- a/web/src/classic/components/organisms/Common/AssetContainer/hooks.ts
+++ b/web/src/classic/components/organisms/Common/AssetContainer/hooks.ts
@@ -9,8 +9,8 @@ import {
Maybe,
AssetSortType as GQLSortType,
} from "@reearth/classic/gql";
-import { useNotification } from "@reearth/classic/state";
import { useT } from "@reearth/services/i18n";
+import { useNotification } from "@reearth/services/state";
export type AssetNodes = NonNullable[];
diff --git a/web/src/classic/components/organisms/Dashboard/hooks.ts b/web/src/classic/components/organisms/Dashboard/hooks.ts
index 70efdd3aed..415a836998 100644
--- a/web/src/classic/components/organisms/Dashboard/hooks.ts
+++ b/web/src/classic/components/organisms/Dashboard/hooks.ts
@@ -14,14 +14,14 @@ import {
Visualizer,
GetProjectsQuery,
} from "@reearth/classic/gql";
+import { useStorytellingFetcher } from "@reearth/services/api";
+import { useT } from "@reearth/services/i18n";
import {
useWorkspace,
useProject,
useUnselectProject,
useNotification,
-} from "@reearth/classic/state";
-import useStorytellingAPI from "@reearth/services/api/storytellingApi";
-import { useT } from "@reearth/services/i18n";
+} from "@reearth/services/state";
import { ProjectType } from "@reearth/types";
export type ProjectNodes = NonNullable[];
@@ -168,7 +168,9 @@ export default (workspaceId?: string) => {
const [createNewProject] = useCreateProjectMutation();
const [createScene] = useCreateSceneMutation({ refetchQueries: ["GetProjects"] });
- const { useCreateStory } = useStorytellingAPI();
+ const { useCreateStory } = useStorytellingFetcher();
+ const { useCreateStoryPage } = useStorytellingFetcher();
+
const handleProjectCreate = useCallback(
async (data: {
name: string;
@@ -221,6 +223,17 @@ export default (workspaceId?: string) => {
});
setModalShown(false);
return;
+ } else {
+ const { errors: storyPageErrors } = await useCreateStoryPage({
+ sceneId: scene.data?.createScene?.scene.id,
+ storyId: story.data.createStory.story.id,
+ });
+ if (storyPageErrors) {
+ setNotification({
+ type: "error",
+ text: t("Failed to create story page on project creation."),
+ });
+ }
}
}
@@ -230,7 +243,15 @@ export default (workspaceId?: string) => {
});
setModalShown(false);
},
- [workspaceId, createNewProject, createScene, setNotification, t, useCreateStory],
+ [
+ workspaceId,
+ createNewProject,
+ createScene,
+ setNotification,
+ t,
+ useCreateStory,
+ useCreateStoryPage,
+ ],
);
const [assetModalOpened, setOpenAssets] = useState(false);
diff --git a/web/src/classic/components/organisms/EarthEditor/CanvasArea/hooks.ts b/web/src/classic/components/organisms/EarthEditor/CanvasArea/hooks.ts
index bd47a4fc5a..0b6053228c 100644
--- a/web/src/classic/components/organisms/EarthEditor/CanvasArea/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/CanvasArea/hooks.ts
@@ -21,6 +21,9 @@ import {
WidgetAreaAlign,
ValueType,
} from "@reearth/classic/gql";
+import { valueTypeToGQL, ValueTypes, valueToGQL, LatLng } from "@reearth/classic/util/value";
+import { config } from "@reearth/services/config";
+import { useLang } from "@reearth/services/i18n";
import {
useSceneId,
useSceneMode,
@@ -32,10 +35,7 @@ import {
useZoomedLayerId,
useClock,
useSelectedWidgetArea,
-} from "@reearth/classic/state";
-import { valueTypeToGQL, ValueTypes, valueToGQL, LatLng } from "@reearth/classic/util/value";
-import { config } from "@reearth/services/config";
-import { useLang } from "@reearth/services/i18n";
+} from "@reearth/services/state";
import {
convertWidgets,
diff --git a/web/src/classic/components/organisms/EarthEditor/DataSourcePane/hooks.ts b/web/src/classic/components/organisms/EarthEditor/DataSourcePane/hooks.ts
index adec57fb8d..7f79878e48 100644
--- a/web/src/classic/components/organisms/EarthEditor/DataSourcePane/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/DataSourcePane/hooks.ts
@@ -12,6 +12,7 @@ import {
useRemoveDatasetMutation,
useGetDatasetSchemasWithCountQuery,
} from "@reearth/classic/gql";
+import { useT, useLang } from "@reearth/services/i18n";
import {
useSceneId,
useNotification,
@@ -19,8 +20,7 @@ import {
useProject,
NotificationType,
useCurrentTheme,
-} from "@reearth/classic/state";
-import { useT, useLang } from "@reearth/services/i18n";
+} from "@reearth/services/state";
export default () => {
const t = useT();
diff --git a/web/src/classic/components/organisms/EarthEditor/DatasetInfoPane/hooks.ts b/web/src/classic/components/organisms/EarthEditor/DatasetInfoPane/hooks.ts
index e76e6c1fac..9f67bb5dc8 100644
--- a/web/src/classic/components/organisms/EarthEditor/DatasetInfoPane/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/DatasetInfoPane/hooks.ts
@@ -5,8 +5,8 @@ import {
useGetDatasetsForDatasetInfoPaneQuery,
useGetScenePluginsForDatasetInfoPaneQuery,
} from "@reearth/classic/gql";
-import { useNotification, useProject, useRootLayerId, useSelected } from "@reearth/classic/state";
import { useT } from "@reearth/services/i18n";
+import { useNotification, useProject, useRootLayerId, useSelected } from "@reearth/services/state";
import { processDatasets, processDatasetHeaders, processPrimitives } from "./convert";
diff --git a/web/src/classic/components/organisms/EarthEditor/ExportPane/hooks.ts b/web/src/classic/components/organisms/EarthEditor/ExportPane/hooks.ts
index fdad30b608..26536f1f52 100644
--- a/web/src/classic/components/organisms/EarthEditor/ExportPane/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/ExportPane/hooks.ts
@@ -1,8 +1,8 @@
import { useCallback } from "react";
import { Format } from "@reearth/classic/components/molecules/EarthEditor/ExportPane";
-import { useRootLayerId, useSelected } from "@reearth/classic/state";
import { useAuth } from "@reearth/services/auth";
+import { useRootLayerId, useSelected } from "@reearth/services/state";
const ext: { [key in Format]: string } = {
kml: "kml",
diff --git a/web/src/classic/components/organisms/EarthEditor/Header/hooks.ts b/web/src/classic/components/organisms/EarthEditor/Header/hooks.ts
index 403aefd8ed..fd8ce59244 100644
--- a/web/src/classic/components/organisms/EarthEditor/Header/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/Header/hooks.ts
@@ -12,9 +12,9 @@ import {
useCheckProjectAliasLazyQuery,
useCreateTeamMutation,
} from "@reearth/classic/gql";
-import { useSceneId, useWorkspace, useProject, useNotification } from "@reearth/classic/state";
import { useAuth } from "@reearth/services/auth";
import { useT } from "@reearth/services/i18n";
+import { useSceneId, useWorkspace, useProject, useNotification } from "@reearth/services/state";
export default () => {
const url = window.REEARTH_CONFIG?.published?.split("{}");
diff --git a/web/src/classic/components/organisms/EarthEditor/LeftMenu/hooks.ts b/web/src/classic/components/organisms/EarthEditor/LeftMenu/hooks.ts
index aefb7fffcb..45ec805cf2 100644
--- a/web/src/classic/components/organisms/EarthEditor/LeftMenu/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/LeftMenu/hooks.ts
@@ -1,4 +1,4 @@
-import { useIsCapturing } from "@reearth/classic/state";
+import { useIsCapturing } from "@reearth/services/state";
export default () => {
const [isCapturing] = useIsCapturing();
diff --git a/web/src/classic/components/organisms/EarthEditor/OutlinePane/hooks.tsx b/web/src/classic/components/organisms/EarthEditor/OutlinePane/hooks.tsx
index c0a0d3823f..10196e8f03 100644
--- a/web/src/classic/components/organisms/EarthEditor/OutlinePane/hooks.tsx
+++ b/web/src/classic/components/organisms/EarthEditor/OutlinePane/hooks.tsx
@@ -27,6 +27,9 @@ import {
PluginExtensionType,
GetLayersFromLayerIdQuery,
} from "@reearth/classic/gql";
+import deepFind from "@reearth/classic/util/deepFind";
+import deepGet from "@reearth/classic/util/deepGet";
+import { useLang, useT } from "@reearth/services/i18n";
import {
useSceneId,
useSelected,
@@ -35,10 +38,7 @@ import {
useWidgetAlignEditorActivated,
useZoomedLayerId,
useSelectedWidgetArea,
-} from "@reearth/classic/state";
-import deepFind from "@reearth/classic/util/deepFind";
-import deepGet from "@reearth/classic/util/deepGet";
-import { useLang, useT } from "@reearth/services/i18n";
+} from "@reearth/services/state";
const convertFormat = (format: Format) => {
if (format === "kml") return LayerEncodingFormat.Kml;
diff --git a/web/src/classic/components/organisms/EarthEditor/PrimitiveHeader/hooks.ts b/web/src/classic/components/organisms/EarthEditor/PrimitiveHeader/hooks.ts
index 06b6fa4c06..c8a63db802 100644
--- a/web/src/classic/components/organisms/EarthEditor/PrimitiveHeader/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/PrimitiveHeader/hooks.ts
@@ -1,8 +1,8 @@
import { useMemo } from "react";
import { useGetPrimitivesQuery, useAddLayerItemFromPrimitiveMutation } from "@reearth/classic/gql";
-import { useSceneId, useSelected } from "@reearth/classic/state";
import { useLang } from "@reearth/services/i18n";
+import { useSceneId, useSelected } from "@reearth/services/state";
// ポリゴンやポリラインは現在編集できないため、それらを新規レイヤーとして追加しても何も表示されない
const hiddenExtensions = ["reearth/polyline", "reearth/polygon", "reearth/rect"];
diff --git a/web/src/classic/components/organisms/EarthEditor/PropertyPane/hooks-queries.ts b/web/src/classic/components/organisms/EarthEditor/PropertyPane/hooks-queries.ts
index 56055edf26..95c3a6aa4e 100644
--- a/web/src/classic/components/organisms/EarthEditor/PropertyPane/hooks-queries.ts
+++ b/web/src/classic/components/organisms/EarthEditor/PropertyPane/hooks-queries.ts
@@ -6,7 +6,7 @@ import {
useGetLinkableDatasetsQuery,
useGetLayersFromLayerIdQuery,
} from "@reearth/classic/gql";
-import { Selected } from "@reearth/classic/state";
+import { Selected } from "@reearth/services/state";
import { convert, Pane, convertLinkableDatasets, convertLayers } from "./convert";
diff --git a/web/src/classic/components/organisms/EarthEditor/PropertyPane/hooks.ts b/web/src/classic/components/organisms/EarthEditor/PropertyPane/hooks.ts
index 0a4990a04e..1f96d32714 100644
--- a/web/src/classic/components/organisms/EarthEditor/PropertyPane/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/PropertyPane/hooks.ts
@@ -23,6 +23,8 @@ import {
WidgetSectionType,
WidgetZoneType,
} from "@reearth/classic/gql";
+import { valueTypeToGQL, Camera, toGQLSimpleValue, valueToGQL } from "@reearth/classic/util/value";
+import { useLang } from "@reearth/services/i18n";
import {
useSelected,
useRootLayerId,
@@ -34,9 +36,7 @@ import {
useSelectedBlock,
useWidgetAlignEditorActivated,
useSelectedWidgetArea,
-} from "@reearth/classic/state";
-import { valueTypeToGQL, Camera, toGQLSimpleValue, valueToGQL } from "@reearth/classic/util/value";
-import { useLang } from "@reearth/services/i18n";
+} from "@reearth/services/state";
import useQueries, { Mode as RawMode } from "./hooks-queries";
diff --git a/web/src/classic/components/organisms/EarthEditor/RightMenu/hooks.ts b/web/src/classic/components/organisms/EarthEditor/RightMenu/hooks.ts
index 073dbf4f3c..3a9427bec3 100644
--- a/web/src/classic/components/organisms/EarthEditor/RightMenu/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/RightMenu/hooks.ts
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react";
-import { useSelected, useSelectedBlock, useIsCapturing } from "@reearth/classic/state";
+import { useSelected, useSelectedBlock, useIsCapturing } from "@reearth/services/state";
export type Tab =
| "layer"
diff --git a/web/src/classic/components/organisms/EarthEditor/TagPane/commonHooks.ts b/web/src/classic/components/organisms/EarthEditor/TagPane/commonHooks.ts
index 3da4e64c45..a0efc61438 100644
--- a/web/src/classic/components/organisms/EarthEditor/TagPane/commonHooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/TagPane/commonHooks.ts
@@ -12,9 +12,9 @@ import {
useRemoveTagMutation,
useUpdateTagMutation,
} from "@reearth/classic/gql";
-import { useNotification, useSceneId, useSelected } from "@reearth/classic/state";
import { useAuth } from "@reearth/services/auth";
import { useT } from "@reearth/services/i18n";
+import { useNotification, useSceneId, useSelected } from "@reearth/services/state";
export default () => {
const { isAuthenticated } = useAuth();
diff --git a/web/src/classic/components/organisms/EarthEditor/core/CanvasArea/hooks.ts b/web/src/classic/components/organisms/EarthEditor/core/CanvasArea/hooks.ts
index e0e7703bf9..666c86fe51 100644
--- a/web/src/classic/components/organisms/EarthEditor/core/CanvasArea/hooks.ts
+++ b/web/src/classic/components/organisms/EarthEditor/core/CanvasArea/hooks.ts
@@ -24,6 +24,14 @@ import {
type WidgetAreaAlign,
ValueType,
} from "@reearth/classic/gql";
+import {
+ valueTypeToGQL,
+ type ValueTypes,
+ valueToGQL,
+ type LatLng,
+} from "@reearth/classic/util/value";
+import { config } from "@reearth/services/config";
+import { useLang } from "@reearth/services/i18n";
import {
useSceneId,
useSceneMode,
@@ -34,15 +42,7 @@ import {
useWidgetAlignEditorActivated,
useZoomedLayerId,
useSelectedWidgetArea,
-} from "@reearth/classic/state";
-import {
- valueTypeToGQL,
- type ValueTypes,
- valueToGQL,
- type LatLng,
-} from "@reearth/classic/util/value";
-import { config } from "@reearth/services/config";
-import { useLang } from "@reearth/services/i18n";
+} from "@reearth/services/state";
import {
convertWidgets,
diff --git a/web/src/classic/components/organisms/GlobalModal/index.tsx b/web/src/classic/components/organisms/GlobalModal/index.tsx
index 3be7d0925a..850939bf07 100644
--- a/web/src/classic/components/organisms/GlobalModal/index.tsx
+++ b/web/src/classic/components/organisms/GlobalModal/index.tsx
@@ -1,12 +1,12 @@
import { useCallback, useEffect, useState } from "react";
+import { useAuth } from "@reearth/services/auth";
+import { useLang as useCurrentLang } from "@reearth/services/i18n";
import {
NotificationType,
useCurrentTheme as useCurrentTheme,
useNotification,
-} from "@reearth/classic/state";
-import { useAuth } from "@reearth/services/auth";
-import { useLang as useCurrentLang } from "@reearth/services/i18n";
+} from "@reearth/services/state";
const GlobalModal: React.FC = () => {
const extensions = window.REEARTH_CONFIG?.extensions?.globalModal;
diff --git a/web/src/classic/components/organisms/Notification/hooks.ts b/web/src/classic/components/organisms/Notification/hooks.ts
deleted file mode 100644
index 7ba909f54c..0000000000
--- a/web/src/classic/components/organisms/Notification/hooks.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { useState, useEffect, useCallback, useMemo } from "react";
-
-import { useError, useNotification, Notification } from "@reearth/classic/state";
-import { useT, useLang } from "@reearth/services/i18n";
-
-export type PolicyItems =
- | "layer"
- | "asset"
- | "dataset"
- | "createProject"
- | "publishProject"
- | "member";
-
-const policyItems: PolicyItems[] = [
- "layer",
- "asset",
- "dataset",
- "createProject",
- "publishProject",
- "member",
-];
-
-export default () => {
- const t = useT();
- const currentLanguage = useLang();
- const [error, setError] = useError();
- const [notification, setNotification] = useNotification();
- const [visible, changeVisibility] = useState(false);
-
- const policyLimitNotifications = window.REEARTH_CONFIG?.policy?.limitNotifications;
-
- const errorHeading = t("Error");
- const warningHeading = t("Warning");
- const noticeHeading = t("Notice");
-
- const notificationHeading = useMemo(
- () =>
- notification?.type === "error"
- ? errorHeading
- : notification?.type === "warning"
- ? warningHeading
- : noticeHeading,
- [notification?.type, errorHeading, warningHeading, noticeHeading],
- );
-
- const resetNotification = useCallback(() => setNotification(undefined), [setNotification]);
-
- const setModal = useCallback((show: boolean) => {
- changeVisibility(show);
- }, []);
-
- useEffect(() => {
- if (!error) return;
- if (error.message?.includes("policy violation") && error.message) {
- const limitedItem = policyItems.find(i => error.message?.includes(i));
- const policyItem =
- limitedItem && policyLimitNotifications ? policyLimitNotifications[limitedItem] : undefined;
- const message = policyItem
- ? typeof policyItem === "string"
- ? policyItem
- : policyItem[currentLanguage]
- : t(
- "You have reached a policy limit. Please contact an administrator of your Re:Earth system.",
- );
-
- setNotification({
- type: "info",
- heading: noticeHeading,
- text: message,
- duration: "persistent",
- });
- } else {
- setNotification({
- type: "error",
- heading: errorHeading,
- text: t("Something went wrong. Please try again later."),
- });
- }
- setError(undefined);
- }, [
- error,
- currentLanguage,
- policyLimitNotifications,
- errorHeading,
- noticeHeading,
- setError,
- setNotification,
- t,
- ]);
-
- useEffect(() => {
- if (!notification) return;
- if (notification.duration === "persistent") return;
-
- let notificationTimeout = 5000;
- if (notification.duration) {
- notificationTimeout = notification.duration;
- }
- const timerID = setTimeout(() => {
- changeVisibility(false);
- }, notificationTimeout);
- return () => clearTimeout(timerID);
- }, [notification]);
-
- useEffect(() => {
- changeVisibility(!!notification);
- }, [notification]);
-
- return {
- visible,
- notification: {
- type: notification?.type,
- heading: notificationHeading,
- text: notification?.text,
- } as Notification,
- setModal,
- resetNotification,
- };
-};
diff --git a/web/src/classic/components/organisms/Notification/index.tsx b/web/src/classic/components/organisms/Notification/index.tsx
deleted file mode 100644
index 6b082dbcff..0000000000
--- a/web/src/classic/components/organisms/Notification/index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from "react";
-
-import MoleculeNotificationBanner from "@reearth/classic/components/atoms/Notification";
-
-import useHooks from "./hooks";
-
-const NotificationBanner: React.FC = () => {
- const { visible, notification, setModal, resetNotification } = useHooks();
-
- return (
-
- );
-};
-
-export default NotificationBanner;
diff --git a/web/src/classic/components/organisms/Published/core/hooks.ts b/web/src/classic/components/organisms/Published/core/hooks.ts
index 9f54132cce..0e147b47f7 100644
--- a/web/src/classic/components/organisms/Published/core/hooks.ts
+++ b/web/src/classic/components/organisms/Published/core/hooks.ts
@@ -17,8 +17,8 @@ import {
} from "@reearth/classic/core/mantle";
import type { ComputedLayer } from "@reearth/classic/core/mantle/types";
import type { LayerSelectionReason } from "@reearth/classic/core/Map/Layers/hooks";
-import { useSelected } from "@reearth/classic/state";
import { config } from "@reearth/services/config";
+import { useSelected } from "@reearth/services/state";
import type {
PublishedData,
diff --git a/web/src/classic/components/organisms/Settings/Account/hooks.ts b/web/src/classic/components/organisms/Settings/Account/hooks.ts
index bac5e5e440..bbb59487b9 100644
--- a/web/src/classic/components/organisms/Settings/Account/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/Account/hooks.ts
@@ -2,8 +2,8 @@ import { useApolloClient } from "@apollo/client";
import { useCallback } from "react";
import { useUpdateMeMutation, useGetProfileQuery, Theme as GQLTheme } from "@reearth/classic/gql";
-import { useWorkspace, useProject, useNotification } from "@reearth/classic/state";
import { useT } from "@reearth/services/i18n";
+import { useWorkspace, useProject, useNotification } from "@reearth/services/state";
const enumTypeMapper: Partial> = {
[GQLTheme.Default]: "default",
diff --git a/web/src/classic/components/organisms/Settings/Project/Dataset/hooks.ts b/web/src/classic/components/organisms/Settings/Project/Dataset/hooks.ts
index 3435d92063..cb85765a5d 100644
--- a/web/src/classic/components/organisms/Settings/Project/Dataset/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/Project/Dataset/hooks.ts
@@ -8,9 +8,9 @@ import {
useRemoveDatasetMutation,
useDatasetsListQuery,
} from "@reearth/classic/gql";
-import { useWorkspace, useProject, useNotification } from "@reearth/classic/state";
import { useAuth } from "@reearth/services/auth";
import { useT } from "@reearth/services/i18n";
+import { useWorkspace, useProject, useNotification } from "@reearth/services/state";
type Nodes = NonNullable;
diff --git a/web/src/classic/components/organisms/Settings/Project/Plugin/hooks.ts b/web/src/classic/components/organisms/Settings/Project/Plugin/hooks.ts
index 9ef10429a6..eec055f681 100644
--- a/web/src/classic/components/organisms/Settings/Project/Plugin/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/Project/Plugin/hooks.ts
@@ -9,9 +9,9 @@ import {
useUploadPluginMutation,
useUpgradePluginMutation,
} from "@reearth/classic/gql/graphql-client-api";
-import { useProject, useNotification, useCurrentTheme } from "@reearth/classic/state";
import { useAuth } from "@reearth/services/auth";
import { useLang, useT } from "@reearth/services/i18n";
+import { useProject, useNotification, useCurrentTheme } from "@reearth/services/state";
export type Plugin = {
fullId: string;
diff --git a/web/src/classic/components/organisms/Settings/Project/Public/hooks.ts b/web/src/classic/components/organisms/Settings/Project/Public/hooks.ts
index 34f4a2e22b..9b98ff475f 100644
--- a/web/src/classic/components/organisms/Settings/Project/Public/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/Project/Public/hooks.ts
@@ -9,14 +9,14 @@ import {
usePublishProjectMutation,
useUpdateProjectMutation,
} from "@reearth/classic/gql";
+import { useLang as useCurrentLang } from "@reearth/services/i18n";
import {
useWorkspace,
useProject,
useNotification,
NotificationType,
useCurrentTheme as useCurrentTheme,
-} from "@reearth/classic/state";
-import { useLang as useCurrentLang } from "@reearth/services/i18n";
+} from "@reearth/services/state";
type Params = {
projectId: string;
diff --git a/web/src/classic/components/organisms/Settings/Project/hooks.ts b/web/src/classic/components/organisms/Settings/Project/hooks.ts
index a9df3e7a0d..9368ee59ab 100644
--- a/web/src/classic/components/organisms/Settings/Project/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/Project/hooks.ts
@@ -6,8 +6,8 @@ import {
useArchiveProjectMutation,
useDeleteProjectMutation,
} from "@reearth/classic/gql";
-import { useWorkspace, useNotification } from "@reearth/classic/state";
import { useT } from "@reearth/services/i18n";
+import { useWorkspace, useNotification } from "@reearth/services/state";
type Params = {
projectId: string;
diff --git a/web/src/classic/components/organisms/Settings/ProjectList/hooks.ts b/web/src/classic/components/organisms/Settings/ProjectList/hooks.ts
index 5c1f7f58af..3d616033c1 100644
--- a/web/src/classic/components/organisms/Settings/ProjectList/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/ProjectList/hooks.ts
@@ -12,9 +12,9 @@ import {
Visualizer,
GetProjectsQuery,
} from "@reearth/classic/gql";
-import { useWorkspace, useProject, useNotification } from "@reearth/classic/state";
import { useMeFetcher } from "@reearth/services/api";
import { useT } from "@reearth/services/i18n";
+import { useWorkspace, useProject, useNotification } from "@reearth/services/state";
import { ProjectType } from "@reearth/types";
const toPublishmentStatus = (s: PublishmentStatus) =>
diff --git a/web/src/classic/components/organisms/Settings/SettingPage/hooks.ts b/web/src/classic/components/organisms/Settings/SettingPage/hooks.ts
index bf3f92cd7d..4949deffe9 100644
--- a/web/src/classic/components/organisms/Settings/SettingPage/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/SettingPage/hooks.ts
@@ -9,7 +9,7 @@ import {
useGetProjectWithSceneIdQuery,
useCreateTeamMutation,
} from "@reearth/classic/gql";
-import { useWorkspace, useProject } from "@reearth/classic/state";
+import { useWorkspace, useProject } from "@reearth/services/state";
type Params = {
workspaceId?: string;
diff --git a/web/src/classic/components/organisms/Settings/Workspace/Asset/hooks.ts b/web/src/classic/components/organisms/Settings/Workspace/Asset/hooks.ts
index c5c98376e3..7c9ed09201 100644
--- a/web/src/classic/components/organisms/Settings/Workspace/Asset/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/Workspace/Asset/hooks.ts
@@ -2,7 +2,7 @@ import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import assetHooks from "@reearth/classic/components/organisms/Common/AssetContainer/hooks";
-import { useWorkspace, useProject } from "@reearth/classic/state";
+import { useWorkspace, useProject } from "@reearth/services/state";
export type Params = {
workspaceId: string;
diff --git a/web/src/classic/components/organisms/Settings/Workspace/hooks.ts b/web/src/classic/components/organisms/Settings/Workspace/hooks.ts
index fca077385a..3b408c6bdb 100644
--- a/web/src/classic/components/organisms/Settings/Workspace/hooks.ts
+++ b/web/src/classic/components/organisms/Settings/Workspace/hooks.ts
@@ -14,8 +14,8 @@ import {
useRemoveMemberFromTeamMutation,
} from "@reearth/classic/gql";
import { Team } from "@reearth/classic/gql/graphql-client-api";
-import { useWorkspace, useProject, useNotification } from "@reearth/classic/state";
import { useT } from "@reearth/services/i18n";
+import { useWorkspace, useProject, useNotification } from "@reearth/services/state";
type Params = {
workspaceId: string;
diff --git a/web/src/classic/components/pages/Authentication/hooks.ts b/web/src/classic/components/pages/Authentication/hooks.ts
index 5d44c67ef6..aedd341ecf 100644
--- a/web/src/classic/components/pages/Authentication/hooks.ts
+++ b/web/src/classic/components/pages/Authentication/hooks.ts
@@ -3,9 +3,9 @@ import { useCallback, useEffect, useMemo } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useGetTeamsQuery } from "@reearth/classic/gql";
-import { useWorkspace, useNotification, useUserId } from "@reearth/classic/state";
import { useAuth, useCleanUrl } from "@reearth/services/auth";
import { useT } from "@reearth/services/i18n";
+import { useWorkspace, useNotification, useUserId } from "@reearth/services/state";
// TODO: move hooks to molecules (page components should be thin)
export default () => {
diff --git a/web/src/classic/components/pages/EarthEditor/hooks.ts b/web/src/classic/components/pages/EarthEditor/hooks.ts
index f1e16009c4..246da03d6a 100644
--- a/web/src/classic/components/pages/EarthEditor/hooks.ts
+++ b/web/src/classic/components/pages/EarthEditor/hooks.ts
@@ -1,8 +1,8 @@
import { useEffect } from "react";
import { useGetSceneQuery } from "@reearth/classic/gql";
-import { useSceneId, useRootLayerId, useZoomedLayerId } from "@reearth/classic/state";
import { useAuth } from "@reearth/services/auth";
+import { useSceneId, useRootLayerId, useZoomedLayerId } from "@reearth/services/state";
export type Mode = "layer" | "widget";
diff --git a/web/src/classic/components/pages/Preview/index.tsx b/web/src/classic/components/pages/Preview/index.tsx
index 24c9ed95a9..eb94f924c8 100644
--- a/web/src/classic/components/pages/Preview/index.tsx
+++ b/web/src/classic/components/pages/Preview/index.tsx
@@ -3,10 +3,10 @@ import { useParams } from "react-router-dom";
import CanvasArea from "@reearth/classic/components/organisms/EarthEditor/CanvasArea";
import CoreCanvasArea from "@reearth/classic/components/organisms/EarthEditor/core/CanvasArea";
-import { useSceneId } from "@reearth/classic/state";
import { useCore } from "@reearth/classic/util/use-core";
import { Provider as DndProvider } from "@reearth/classic/util/use-dnd";
import { AuthenticatedPage } from "@reearth/services/auth";
+import { useSceneId } from "@reearth/services/state";
import { PublishedAppProvider as ThemeProvider } from "@reearth/services/theme";
export type Props = {
diff --git a/web/src/classic/gql/graphql-client-api.tsx b/web/src/classic/gql/graphql-client-api.tsx
index 7eb65e1733..065b389ee5 100644
--- a/web/src/classic/gql/graphql-client-api.tsx
+++ b/web/src/classic/gql/graphql-client-api.tsx
@@ -22,6 +22,7 @@ export type Scalars = {
FileSize: { input: number; output: number; }
JSON: { input: any; output: any; }
Lang: { input: string; output: string; }
+ Map: { input: any; output: any; }
TranslatedString: { input: { [lang in string]?: string } | null; output: { [lang in string]?: string } | null; }
URL: { input: string; output: string; }
Upload: { input: any; output: any; }
@@ -2122,6 +2123,7 @@ export type Story = Node & {
alias: Scalars['String']['output'];
basicAuthPassword: Scalars['String']['output'];
basicAuthUsername: Scalars['String']['output'];
+ bgColor?: Maybe;
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
isBasicAuthActive: Scalars['Boolean']['output'];
@@ -2440,6 +2442,7 @@ export type UpdateStoryInput = {
alias?: InputMaybe