Skip to content

Commit

Permalink
chore(web): hook up with story page API (#592)
Browse files Browse the repository at this point in the history
Co-authored-by: YK <[email protected]>
  • Loading branch information
isoppp and yk-eukarya authored Jul 31, 2023
1 parent f9b3105 commit 9eea0f9
Show file tree
Hide file tree
Showing 28 changed files with 979 additions and 133 deletions.
3 changes: 0 additions & 3 deletions web/src/beta/components/DragAndDropList/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,6 @@ const Item: FC<Props> = ({
onItemMove(dragIndex, hoverIndex);
item.index = hoverIndex;
},
drop(item) {
onItemDropOnItem(item.index);
},
});

const [{ isDragging }, drag] = useDrag({
Expand Down
7 changes: 4 additions & 3 deletions web/src/beta/components/DragAndDropList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Props<Item extends { id: string } = { id: string }> = {
items: Item[];
getId: (item: Item) => string;
onItemDrop(item: Item, targetIndex: number): void;
renderItem: (item: Item) => ReactNode;
renderItem: (item: Item, index: number) => ReactNode;
gap: number;
};

Expand Down Expand Up @@ -40,9 +40,10 @@ function DragAndDropList<Item extends { id: string } = { id: string }>({
const onItemDropOnItem = useCallback(
(index: number) => {
const item = movingItems[index];
if (items[index].id === item.id) return;
item && onItemDrop(movingItems[index], index);
},
[movingItems, onItemDrop],
[items, movingItems, onItemDrop],
);

const onItemDropOutside = useCallback(() => {
Expand All @@ -62,7 +63,7 @@ function DragAndDropList<Item extends { id: string } = { id: string }>({
onItemMove={onItemMove}
onItemDropOnItem={onItemDropOnItem}
onItemDropOutside={onItemDropOutside}>
{renderItem(item)}
{renderItem(item, i)}
</Item>
);
})}
Expand Down
40 changes: 35 additions & 5 deletions web/src/beta/features/Editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ import StoryPanel from "@reearth/beta/features/Editor/tabs/story/StoryPanel";
import useLeftPanel from "@reearth/beta/features/Editor/useLeftPanel";
import useRightPanel from "@reearth/beta/features/Editor/useRightPanel";
import useSecondaryNavbar from "@reearth/beta/features/Editor/useSecondaryNavbar";
import useStorytelling from "@reearth/beta/features/Editor/useStorytelling";
import Visualizer from "@reearth/beta/features/Editor/Visualizer";
import Navbar, { type Tab } from "@reearth/beta/features/Navbar";
import { Provider as DndProvider } from "@reearth/beta/utils/use-dnd";
import { StoryFragmentFragment } from "@reearth/services/gql";
import { metrics, styled } from "@reearth/services/theme";

import useHooks from "./hooks";
import { navbarHeight } from "./SecondaryNav";

type Props = {
sceneId: string;
projectId?: string; // gotten through injection
workspaceId?: string; // gotten through injection
tab: Tab;
projectId?: string;
workspaceId?: string;
stories: StoryFragmentFragment[];
};

const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab }) => {
const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab, stories }) => {
const {
selectedDevice,
visualizerWidth,
Expand All @@ -27,7 +30,28 @@ const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab }) => {
handleWidgetEditorToggle,
} = useHooks({ tab });

const { leftPanel } = useLeftPanel({ tab });
const {
selectedStory,
selectedPage,
onPageSelect,
onPageDuplicate,
onPageDelete,
onPageAdd,
onPageMove,
} = useStorytelling({
sceneId,
stories,
});
const { leftPanel } = useLeftPanel({
tab,
selectedStory,
selectedPage,
onPageSelect,
onPageDuplicate,
onPageDelete,
onPageAdd,
onPageMove,
});
const { rightPanel } = useRightPanel({ tab, sceneId });
const { secondaryNavbar } = useSecondaryNavbar({
tab,
Expand Down Expand Up @@ -61,7 +85,13 @@ const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab }) => {
<Center>
{secondaryNavbar}
<CenterContents hasNav={!!secondaryNavbar}>
{isStory && <StoryPanel />}
{isStory && (
<StoryPanel
selectedStory={selectedStory}
selectedPage={selectedPage}
onPageSelect={onPageSelect}
/>
)}
<VisualizerWrapper tab={tab} visualizerWidth={visualizerWidth}>
<Visualizer sceneId={sceneId} />
</VisualizerWrapper>
Expand Down
50 changes: 24 additions & 26 deletions web/src/beta/features/Editor/tabs/story/PageIndicator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from "react";
import { FC, useMemo } from "react";

import { styled } from "@reearth/services/theme";

Expand All @@ -15,51 +15,49 @@ const StoryPageIndicator: FC<Props> = ({
maxPage,
onPageChange,
}) => {
const widthPercentage = useMemo(() => {
const onePageWidth = 100 / maxPage;
const base = (currentPage - 1) * onePageWidth;
const progress = (onePageWidth / 100) * currentPageProgress;
return base + progress;
}, [currentPage, currentPageProgress, maxPage]);
return (
<Wrapper>
<Wrapper widthPercentage={widthPercentage}>
{[...Array(maxPage)].map((_, i) => {
const page = i + 1;
const isActive = currentPage >= page;
const isCurrentPage = page === currentPage;
const progress = isCurrentPage ? currentPageProgress : isActive ? 100 : 0;
return (
<Indicator key={i} progress={progress} type="button" onClick={() => onPageChange(page)} />
);
return <Indicator key={i} type="button" onClick={() => onPageChange(i + 1)} />;
})}
</Wrapper>
);
};

export default StoryPageIndicator;

const Wrapper = styled.div`
// TODO: fix colors/transitions including hover
const Wrapper = styled.div<{ widthPercentage: number }>`
position: relative;
display: flex;
background-color: #c2deff;
:after {
content: "";
position: absolute;
inset: 0;
background-color: #3592ff;
transition: width 0.2s ease-out;
width: ${({ widthPercentage }) => widthPercentage}%;
}
`;

// TODO: fix colors/transitions including hover
const Indicator = styled.button<{ progress: number }>`
const Indicator = styled.button`
position: relative;
flex: 1;
height: 8px;
background-color: #c2deff;
transition: all 0.15s;
z-index: 1;
:hover {
opacity: 0.8;
}
:not(:first-of-type) {
border-left: 1px solid #ffffff;
}
:after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: ${({ progress }) => progress}%;
background-color: #3592ff;
transition: width 0.15s;
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export default {

type Story = StoryObj<typeof ContentPage>;

const dummyPages = [...Array(25)].map((_, i) => ({
id: i.toString(),
title: `Page ${i}`,
swipeable: i % 2 === 0,
}));

export const Default: Story = {
render: args => {
return (
Expand All @@ -16,4 +22,9 @@ export const Default: Story = {
</div>
);
},
args: {
// need API mock
storyPages: dummyPages,
selectedPage: dummyPages[1],
},
};
Original file line number Diff line number Diff line change
@@ -1,35 +1,40 @@
import { useState } from "react";
import { useEffect, useState } from "react";

import DragAndDropList from "@reearth/beta/components/DragAndDropList";
import ListItem from "@reearth/beta/components/ListItem";
import PopoverMenuContent from "@reearth/beta/components/PopoverMenuContent";
import Action from "@reearth/beta/features/Editor/tabs/story/SidePanel/Action";
import PageItemWrapper from "@reearth/beta/features/Editor/tabs/story/SidePanel/PageItemWrapper";
import { StoryPageFragmentFragment } from "@reearth/services/gql";
import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

type Props = {
storyPages: StoryPageFragmentFragment[];
selectedPage?: StoryPageFragmentFragment;
onPageSelect: (id: string) => void;
onPageAdd: () => void;
onPageAdd: (isSwipeable: boolean) => void;
onPageDuplicate: (id: string) => void;
onPageDelete: (id: string) => void;
onPageMove: (id: string, targetIndex: number) => void;
};
const ContentPage: React.FC<Props> = ({
storyPages,
selectedPage,
onPageSelect,
onPageAdd,
onPageDuplicate,
onPageDelete,
onPageMove,
}) => {
const t = useT();
const [openedPageId, setOpenedPageId] = useState<string | undefined>(undefined);

const [items, setItems] = useState(
[...Array(100)].map((_, i) => ({
id: i.toString(),
index: i,
text: "page" + i,
})),
);
const [items, setItems] = useState(storyPages);

useEffect(() => {
setItems(storyPages);
}, [storyPages]);
return (
<SContent>
<SContentUp onScroll={openedPageId ? () => setOpenedPageId(undefined) : undefined}>
Expand All @@ -38,7 +43,7 @@ const ContentPage: React.FC<Props> = ({
gap={8}
items={items}
getId={item => item.id}
onItemDrop={(item, index) => {
onItemDrop={async (item, index) => {
setItems(old => {
const items = [...old];
items.splice(
Expand All @@ -48,19 +53,24 @@ const ContentPage: React.FC<Props> = ({
items.splice(index, 0, item);
return items;
});
await onPageMove(item.id, index);
}}
renderItem={item => {
renderItem={(storyPage, i) => {
return (
<PageItemWrapper pageCount={item.index + 1} isSwipable={item.index % 2 === 0}>
<PageItemWrapper
key={storyPage.id}
pageCount={i + 1}
isSwipeable={storyPage.swipeable}>
<ListItem
key={i}
border
onItemClick={() => onPageSelect(item.id)}
onActionClick={() => setOpenedPageId(old => (old ? undefined : item.id))}
onItemClick={() => onPageSelect(storyPage.id)}
onActionClick={() => setOpenedPageId(old => (old ? undefined : storyPage.id))}
onOpenChange={isOpen => {
setOpenedPageId(isOpen ? item.id : undefined);
setOpenedPageId(isOpen ? storyPage.id : undefined);
}}
isSelected={item.index === 0}
isOpenAction={openedPageId === item.id}
isSelected={selectedPage?.id === storyPage.id}
isOpenAction={openedPageId === storyPage.id}
actionContent={
<PopoverMenuContent
width="120px"
Expand All @@ -71,30 +81,30 @@ const ContentPage: React.FC<Props> = ({
name: "Duplicate",
onClick: () => {
setOpenedPageId(undefined);
onPageDuplicate(item.id);
onPageDuplicate(storyPage.id);
},
},
{
icon: "trash",
name: "Delete",
onClick: () => {
setOpenedPageId(undefined);
onPageDelete(item.id);
onPageDelete(storyPage.id);
},
},
]}
/>
}>
Page
{storyPage.title}
</ListItem>
</PageItemWrapper>
);
}}
/>
</SContentUp>
<SContentBottom>
<Action icon="square" title={`+ ${t("New Page")}`} onClick={onPageAdd} />
<Action icon="swiper" title={`+ ${t("New Swipe")}`} onClick={onPageAdd} />
<Action icon="square" title={`+ ${t("New Page")}`} onClick={() => onPageAdd(false)} />
<Action icon="swiper" title={`+ ${t("New Swipe")}`} onClick={() => onPageAdd(true)} />
</SContentBottom>
</SContent>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const ContentStory: React.FC<Props> = ({
}}
renderItem={item => {
return (
<PageItemWrapper pageCount={item.index + 1} isSwipable={item.index % 2 === 0}>
<PageItemWrapper pageCount={item.index + 1} isSwipeable={item.index % 2 === 0}>
<ListItem
border
onItemClick={() => onStorySelect(item.id)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Story = StoryObj<typeof ActionItem>;
export const Default: Story = {
args: {
pageCount: 10,
isSwipable: true,
isSwipeable: true,
children: <div>test</div>,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import { styled } from "@reearth/services/theme";
type Props = {
children: ReactNode;
pageCount: number;
isSwipable: boolean;
isSwipeable: boolean;
};

const StorySidePanelPageWrapper: FC<Props> = ({ children, pageCount, isSwipable }) => {
const StorySidePanelPageWrapper: FC<Props> = ({ children, pageCount, isSwipeable }) => {
return (
<Wrapper>
<Left>
<div>
<Text size="footnote">{pageCount}</Text>
</div>
<div>
<Icon icon={isSwipable ? "swiper" : "square"} color="#4A4A4A" size={12} />
<Icon icon={isSwipeable ? "swiper" : "square"} color="#4A4A4A" size={12} />
</div>
</Left>
<Right>{children}</Right>
Expand Down
Loading

0 comments on commit 9eea0f9

Please sign in to comment.