Skip to content

Commit

Permalink
chore(web): reset current page on tab change (#822)
Browse files Browse the repository at this point in the history
  • Loading branch information
KaWaite committed Nov 17, 2023
1 parent d42279c commit 972fda8
Show file tree
Hide file tree
Showing 15 changed files with 128 additions and 110 deletions.
9 changes: 8 additions & 1 deletion web/src/beta/components/Popover/index.tsx
Expand Up @@ -7,6 +7,7 @@ import {
import * as React from "react";

import { PopoverContext, usePopoverContext } from "@reearth/beta/components/Popover/context";
import { useTheme } from "@reearth/services/theme";

import usePopover from "./hooks";
import { PopoverOptions } from "./types";
Expand Down Expand Up @@ -72,6 +73,7 @@ export const Content = React.forwardRef<
React.HTMLProps<HTMLDivElement> & ContentProps
>(function Content({ style, className, attachToRoot = false, ...props }, propRef) {
const { context: floatingContext, ...context } = usePopoverContext();
const theme = useTheme();
const ref = useMergeRefs([context.refs.setFloating, propRef]);
const { isMounted, styles: transitionStyles } = useTransitionStyles(floatingContext, {
duration: 50,
Expand All @@ -87,7 +89,12 @@ export const Content = React.forwardRef<
<div
ref={ref}
className={className}
style={{ ...context.floatingStyles, ...transitionStyles, ...style }}
style={{
...context.floatingStyles,
...transitionStyles,
...style,
zIndex: theme.zIndexes.editor.popover,
}}
{...context.getFloatingProps(props)}>
{props.children}
</div>
Expand Down
4 changes: 3 additions & 1 deletion web/src/beta/components/fields/SelectField/index.tsx
Expand Up @@ -20,6 +20,7 @@ type CommonProps = {
// Property field
name?: string;
description?: string;
attachToRoot?: boolean;
};

export type SingleSelectProps = {
Expand All @@ -46,6 +47,7 @@ const SelectField: React.FC<Props> = ({
disabled = false,
name,
description,
attachToRoot,
}) => {
const t = useT();

Expand Down Expand Up @@ -110,7 +112,7 @@ const SelectField: React.FC<Props> = ({
<ArrowIcon icon="arrowDown" open={open} size={12} />
</InputWrapper>
</Popover.Trigger>
<PickerWrapper attachToRoot>
<PickerWrapper attachToRoot={attachToRoot}>
{options?.map(({ label: value, key }) => (
<OptionWrapper
key={key}
Expand Down
3 changes: 1 addition & 2 deletions web/src/beta/components/fields/common/TextInput/index.tsx
Expand Up @@ -36,9 +36,8 @@ const TextInput: React.FC<Props> = ({
const newValue = e.currentTarget.value;
if (newValue === undefined) return;
setCurrentValue(newValue);
onChange?.(newValue);
},
[onChange],
[],
);

const handleBlur = useCallback(() => {
Expand Down
Expand Up @@ -32,6 +32,7 @@ const SelectDataType: React.FC<{ fileFormat: string; setFileFormat: (k: string)
options={["GeoJSON", "KML", "CZML"].map(v => ({ key: v, label: v }))}
name={t("File Format")}
description={t("File format of the data source you want to add.")}
attachToRoot
onChange={setFileFormat}
/>
);
Expand Down
7 changes: 7 additions & 0 deletions web/src/beta/features/Editor/index.tsx
Expand Up @@ -43,6 +43,7 @@ const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab }) => {
handleCameraUpdate,
handlePropertyValueUpdate,
} = useHooks({ sceneId, tab });

const {
selectedStory,
storyPanelRef,
Expand Down Expand Up @@ -178,6 +179,11 @@ const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab }) => {
handleWidgetEditorToggle,
});

const handleTabChange = useCallback(
() => storyPanelRef.current?.handleCurrentPageChange(undefined),
[storyPanelRef],
);

return (
<DndProvider>
<Wrapper>
Expand All @@ -186,6 +192,7 @@ const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab }) => {
projectId={projectId}
workspaceId={workspaceId}
currentTab={tab}
onTabChange={handleTabChange}
/>
<MainSection>
{leftPanel && (
Expand Down
3 changes: 3 additions & 0 deletions web/src/beta/features/Navbar/index.tsx
Expand Up @@ -11,6 +11,7 @@ type Props = {
isDashboard?: boolean;
currentTab?: Tab;
page?: "editor" | "settings";
onTabChange?: () => void;
};

export const Tabs = ["map", "story", "widgets", "publish"] as const;
Expand All @@ -29,6 +30,7 @@ const Navbar: React.FC<Props> = ({
currentTab = "map",
isDashboard = false,
page = "editor",
onTabChange,
}) => {
const {
currentProject,
Expand All @@ -48,6 +50,7 @@ const Navbar: React.FC<Props> = ({
currentTab,
sceneId,
page,
onTabChange,
});

return (
Expand Down
5 changes: 3 additions & 2 deletions web/src/beta/features/Navbar/useRightSection.tsx
Expand Up @@ -11,11 +11,12 @@ type Props = {
currentTab?: Tab;
sceneId?: string;
page: "editor" | "settings";
onTabChange?: () => void;
};

const useRightSide = ({ currentTab, page, sceneId }: Props) => {
const useRightSide = ({ currentTab, page, sceneId, onTabChange }: Props) => {
const t = useT();
const handleEditorNavigation = useEditorNavigation({ sceneId });
const handleEditorNavigation = useEditorNavigation({ sceneId, onTabChange });

const rightSide = useMemo(() => {
if (page === "editor") {
Expand Down
13 changes: 11 additions & 2 deletions web/src/beta/hooks/navigationHooks.ts
Expand Up @@ -3,14 +3,22 @@ import { useNavigate } from "react-router-dom";

import { Tab } from "@reearth/beta/features/Navbar";

export const useEditorNavigation = ({ sceneId }: { sceneId?: string }) => {
export const useEditorNavigation = ({
sceneId,
onTabChange,
}: {
sceneId?: string;
onTabChange?: () => void;
}) => {
const navigate = useNavigate();

const handleNavigate = useCallback(
(tab: Tab) => {
if (!sceneId) return;
onTabChange?.();
navigate(`/scene/${sceneId}/${tab}`);
},
[sceneId, navigate],
[sceneId, onTabChange, navigate],
);

return sceneId ? handleNavigate : undefined;
Expand All @@ -21,6 +29,7 @@ export const useSettingsNavigation = ({ projectId }: { projectId?: string }) =>

const handleNavigate = useCallback(
(page?: "public" | "story" | "asset" | "plugin") => {
if (!projectId || !page) return;
navigate(`/settings/project/${projectId}/${page}`);
},
[projectId, navigate],
Expand Down
2 changes: 1 addition & 1 deletion web/src/beta/lib/core/StoryPanel/ActionPanel/index.tsx
Expand Up @@ -122,7 +122,7 @@ const ActionPanel: React.FC<Props> = ({
),
)}
</BlockOptions>
<StyledPopoverContent>
<StyledPopoverContent attachToRoot>
{showPadding ? (
<SettingsDropdown>
<SettingsHeading>
Expand Down
41 changes: 39 additions & 2 deletions web/src/beta/lib/core/StoryPanel/Page/index.tsx
@@ -1,4 +1,4 @@
import { Fragment } from "react";
import { Fragment, MutableRefObject, useEffect } from "react";

import DragAndDropList from "@reearth/beta/components/DragAndDropList";
import type { Spacing, ValueType, ValueTypes } from "@reearth/beta/utils/value";
Expand All @@ -7,6 +7,8 @@ import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

import StoryBlock from "../Block";
import { STORY_PANEL_CONTENT_ELEMENT_ID } from "../constants";
import { useElementOnScreen } from "../hooks/useElementOnScreen";
import SelectableArea from "../SelectableArea";

import BlockAddBar from "./BlockAddBar";
Expand All @@ -19,6 +21,9 @@ type Props = {
selectedStoryBlockId?: string;
showPageSettings?: boolean;
isEditable?: boolean;
isAutoScrolling?: MutableRefObject<boolean>;
scrollTimeoutRef: MutableRefObject<NodeJS.Timeout | undefined>;
onCurrentPageChange?: (pageId: string, disableScrollIntoView?: boolean) => void;
onPageSettingsToggle?: () => void;
onPageSelect?: (pageId?: string | undefined) => void;
onBlockCreate?: (
Expand Down Expand Up @@ -58,6 +63,9 @@ const StoryPanel: React.FC<Props> = ({
selectedStoryBlockId,
showPageSettings,
isEditable,
scrollTimeoutRef,
isAutoScrolling,
onCurrentPageChange,
onPageSettingsToggle,
onPageSelect,
onBlockCreate,
Expand Down Expand Up @@ -87,6 +95,31 @@ const StoryPanel: React.FC<Props> = ({
onBlockCreate,
});

const { containerRef, isIntersecting } = useElementOnScreen({
root: document.getElementById(STORY_PANEL_CONTENT_ELEMENT_ID),
threshold: 0.2,
});

useEffect(() => {
if (isIntersecting) {
const id = containerRef.current?.id;
if (id) {
if (isAutoScrolling?.current) {
const wrapperElement = document.getElementById(STORY_PANEL_CONTENT_ELEMENT_ID);

wrapperElement?.addEventListener("scroll", () => {
clearTimeout(scrollTimeoutRef.current);
scrollTimeoutRef.current = setTimeout(function () {
isAutoScrolling.current = false;
}, 100);
});
} else {
onCurrentPageChange?.(id, true);
}
}
}
}, [isIntersecting, containerRef, isAutoScrolling, scrollTimeoutRef, onCurrentPageChange]);

return (
<SelectableArea
title={page?.title ?? t("Page")}
Expand All @@ -100,7 +133,11 @@ const StoryPanel: React.FC<Props> = ({
isEditable={isEditable}
onClick={() => onPageSelect?.(page?.id)}
onSettingsToggle={onPageSettingsToggle}>
<Wrapper id={page?.id} padding={panelSettings.padding.value} gap={panelSettings.gap.value}>
<Wrapper
id={page?.id}
ref={containerRef}
padding={panelSettings.padding.value}
gap={panelSettings.gap.value}>
{(isEditable || title?.title?.value) && (
<StoryBlock
block={{
Expand Down
74 changes: 2 additions & 72 deletions web/src/beta/lib/core/StoryPanel/PanelContent/hooks.ts
@@ -1,32 +1,22 @@
import { MutableRefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";

import { STORY_PANEL_CONTENT_ELEMENT_ID } from "../constants";
import type { StoryPage } from "../hooks";

export type { StoryPage } from "../hooks";
export { STORY_PANEL_CONTENT_ELEMENT_ID } from "../constants";

export default ({
pages,
currentPageId,
isAutoScrolling,
onBlockCreate,
onBlockDelete,
onCurrentPageChange,
}: {
pages?: StoryPage[];
currentPageId?: string;
isAutoScrolling?: MutableRefObject<boolean>;
onBlockCreate?: (
pageId?: string | undefined,
extensionId?: string | undefined,
pluginId?: string | undefined,
index?: number | undefined,
) => Promise<void>;
onBlockDelete?: (pageId?: string | undefined, blockId?: string | undefined) => Promise<void>;
onCurrentPageChange?: (pageId: string, disableScrollIntoView?: boolean) => void;
}) => {
const scrollRef = useRef<number | undefined>(undefined);
const scrollTimeoutRef = useRef<NodeJS.Timeout>();

const [pageGap, setPageGap] = useState<number>();
Expand Down Expand Up @@ -61,69 +51,9 @@ export default ({
return () => window.removeEventListener("resize", resizeCallback);
}, []);

useEffect(() => {
const ids = pages?.map(p => p.id) as string[];
const panelContentElement = document.getElementById(STORY_PANEL_CONTENT_ELEMENT_ID);

const observer = new IntersectionObserver(
entries => {
// to avoid conflicts with page selection in core's parent
if (isAutoScrolling?.current) {
const wrapperElement = document.getElementById(STORY_PANEL_CONTENT_ELEMENT_ID);

wrapperElement?.addEventListener("scroll", () => {
clearTimeout(scrollTimeoutRef.current);
scrollTimeoutRef.current = setTimeout(function () {
isAutoScrolling.current = false;
}, 100);
});

return;
}

entries.forEach(entry => {
const id = entry.target.getAttribute("id") ?? "";
if (!id ?? currentPageId === id) return;

const diff = (scrollRef.current as number) - (panelContentElement?.scrollTop as number);
const isScrollingUp = diff > 0;

if (entry.isIntersecting) {
onCurrentPageChange?.(id, true);
scrollRef.current = panelContentElement?.scrollTop;
return;
}
const currentIndex = ids?.indexOf(id) as number;
const prevEntry = ids[currentIndex - 1];
if (isScrollingUp) {
const id = prevEntry;
onCurrentPageChange?.(id, true);
}
});
},
{
root: panelContentElement,
threshold: 0.2,
},
);
ids?.forEach(id => {
const e = document.getElementById(id);
if (e) {
observer.observe(e);
}
});
return () => {
ids?.forEach(id => {
const e = document.getElementById(id);
if (e) {
observer.unobserve(e);
}
});
};
}, [pages, currentPageId, isAutoScrolling, onCurrentPageChange]);

return {
pageGap,
scrollTimeoutRef,
handleBlockCreate,
handleBlockDelete,
};
Expand Down

0 comments on commit 972fda8

Please sign in to comment.