Skip to content

Commit

Permalink
chore(web): basic publish tab in beta (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
KaWaite authored Aug 2, 2023
1 parent aa8680f commit 5f31419
Show file tree
Hide file tree
Showing 17 changed files with 1,081 additions and 103 deletions.
2 changes: 1 addition & 1 deletion web/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Suspense } from "react";

import Loading from "@reearth/beta/components/Loading";
import NotificationBanner from "@reearth/classic/components/organisms/Notification";
import NotificationBanner from "@reearth/beta/features/Notification";
import { Provider as I18nProvider } from "@reearth/services/i18n";

import { AuthProvider } from "./services/auth";
Expand Down
3 changes: 3 additions & 0 deletions web/src/beta/components/Icon/Icons/alert.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions web/src/beta/components/Icon/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import Help from "./Icons/help.svg";
import CheckMark from "./Icons/checkMark.svg";
import Plus from "./Icons/plus.svg";
import Minus from "./Icons/minus.svg";
import Alert from "./Icons/alert.svg";

// Dataset
import File from "./Icons/fileIcon.svg";
Expand Down Expand Up @@ -132,6 +133,7 @@ export default {
workspaces: Workspaces,
checkmark: CheckMark,
minus: Minus,
alert: Alert,
logo: Logo,
logoColorful: LogoColorful,
desktop: Desktop,
Expand Down
53 changes: 32 additions & 21 deletions web/src/beta/components/Modal/ModalFrame/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useTransition, { TransitionStatus } from "@rot1024/use-transition";
import { ReactNode, useRef, useCallback } from "react";
import { ReactNode, useRef, useCallback, useMemo } from "react";
import { useClickAway, useKeyPressEvent } from "react-use";

import Icon from "@reearth/beta/components/Icon";
Expand All @@ -19,6 +19,11 @@ const Modal: React.FC<Props> = ({ className, size, isVisible, title, onClose, ch
const ref = useRef<HTMLDivElement>(null);
useClickAway(ref, () => onClose?.());

const modalWidth = useMemo(
() => (size === "sm" ? "416px" : size === "lg" ? "778px" : "572px"),
[size],
);

const state = useTransition(!!isVisible, 300, {
mountOnEnter: true,
unmountOnExit: true,
Expand All @@ -32,17 +37,19 @@ const Modal: React.FC<Props> = ({ className, size, isVisible, title, onClose, ch

return state === "unmounted" ? null : (
<Bg state={state}>
<Wrapper className={className} ref={ref} size={size}>
{!!title && (
<HeaderWrapper>
<ModalTitle size="body" weight="regular" color="#E0E0E0">
{title}
</ModalTitle>
{onClose && <CloseIcon icon="cancel" onClick={onClose} />}
</HeaderWrapper>
)}
<InnerWrapper>{children}</InnerWrapper>
</Wrapper>
<CenteredWrapper width={modalWidth}>
<Wrapper className={className} ref={ref} width={modalWidth}>
{!!title && (
<HeaderWrapper>
<ModalTitle size="body" weight="regular" color="#E0E0E0">
{title}
</ModalTitle>
{onClose && <CloseIcon icon="cancel" onClick={onClose} />}
</HeaderWrapper>
)}
<InnerWrapper>{children}</InnerWrapper>
</Wrapper>
</CenteredWrapper>
</Bg>
);
};
Expand All @@ -61,13 +68,21 @@ const Bg = styled.div<{ state: TransitionStatus }>`
opacity: ${({ state }) => (state === "entered" || state === "entering" ? 1 : 0)};
`;

const Wrapper = styled.div<{ size?: string }>`
margin: ${({ size }) => (size === "sm" ? "15%" : size === "lg" ? "4%" : "8%")} auto;
padding-top: 36px;
const CenteredWrapper = styled.div<{ width?: string }>`
margin-left: auto;
margin-right: auto;
height: 100%;
width: ${({ width }) => width};
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
`;

const Wrapper = styled.div<{ width?: string }>`
border-radius: 8px;
background: #161616;
width: ${({ size }) => (size === "sm" ? "372px" : size === "lg" ? "684px" : "620px")};
position: relative;
width: ${({ width }) => width};
`;

const InnerWrapper = styled.div`
Expand All @@ -93,9 +108,5 @@ const HeaderWrapper = styled.div`
border-top-right-radius: 8px;
border-top-left-radius: 8px;
background: #393939;
position: absolute;
top: 0;
left: 0;
right: 0;
`;
export default Modal;
7 changes: 5 additions & 2 deletions web/src/beta/components/PopoverMenuContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type MenuItem = {
name: string;
isSelected?: boolean;
icon?: Icons;
disabled?: boolean;
onClick?: () => void;
};

Expand Down Expand Up @@ -44,6 +45,7 @@ const PopoverMenuContent: React.FC<Props> = ({ size, width, items }) => {
isFirst={i === 0}
isLast={i === items.length - 1}
size={size}
disabled={!!item.disabled}
onClick={item.onClick}>
{item.icon && (
<SLeftIcon>
Expand Down Expand Up @@ -77,14 +79,15 @@ const SRow = styled.button<
display: flex;
align-items: center;
gap: 8px;
color: ${({ theme }) => theme.content.main};
color: ${({ theme, disabled }) => (disabled ? theme.content.weak : theme.content.main)};
${({ disabled }) => disabled && "cursor: default;"}
${({ isFirst }) => !isFirst && "border-top: 1px solid transparent;"}
${({ isLast }) => !isLast && "border-bottom: 1px solid transparent;"}
${({ size }) => stylesBySize[size].row ?? ""}
:hover {
background: ${({ isSelected, theme }) => !isSelected && theme.bg[2]};
background: ${({ isSelected, theme, disabled }) => !disabled && !isSelected && theme.bg[2]};
}
`;

Expand Down
29 changes: 29 additions & 0 deletions web/src/beta/features/Editor/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,34 @@ import { useWidgetAlignEditorActivated } from "@reearth/services/state";

import { Tab } from "../Navbar";

import { type ProjectType } from "./tabs/publish/Nav";
import { type Device } from "./tabs/widgets/Nav";

export default ({ tab }: { tab: Tab }) => {
const [selectedDevice, setDevice] = useState<Device>("desktop");

const [selectedProjectType, setSelectedProjectType] = useState<ProjectType>(
tab === "story" ? "story" : "default",
);

const [showWidgetEditor, setWidgetEditor] = useWidgetAlignEditorActivated();

useEffect(() => {
switch (tab) {
case "story":
if (selectedProjectType !== "story") {
setSelectedProjectType("story");
}
break;
case "scene":
case "widgets":
if (selectedProjectType === "story") {
setSelectedProjectType("default");
}
break;
}
}, [tab, selectedProjectType, setSelectedProjectType]);

useEffect(() => {
if (tab !== "widgets" && showWidgetEditor) {
setWidgetEditor(false);
Expand All @@ -19,6 +41,11 @@ export default ({ tab }: { tab: Tab }) => {

const handleDeviceChange = useCallback((newDevice: Device) => setDevice(newDevice), []);

const handleProjectTypeChange = useCallback(
(projectType: ProjectType) => setSelectedProjectType(projectType),
[],
);

const visualizerWidth = useMemo(
() => (tab === "widgets" ? devices[selectedDevice] : "100%"),
[tab, selectedDevice],
Expand All @@ -31,9 +58,11 @@ export default ({ tab }: { tab: Tab }) => {

return {
selectedDevice,
selectedProjectType,
visualizerWidth,
showWidgetEditor,
handleDeviceChange,
handleProjectTypeChange,
handleWidgetEditorToggle,
};
};
9 changes: 6 additions & 3 deletions web/src/beta/features/Editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ type Props = {
const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab, stories }) => {
const {
selectedDevice,
selectedProjectType,
visualizerWidth,
showWidgetEditor,
handleDeviceChange,
handleProjectTypeChange,
handleWidgetEditorToggle,
} = useHooks({ tab });

Expand Down Expand Up @@ -55,14 +57,15 @@ const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab, stories
const { rightPanel } = useRightPanel({ tab, sceneId });
const { secondaryNavbar } = useSecondaryNavbar({
tab,
projectId,
selectedDevice,
selectedProjectType,
showWidgetEditor,
handleProjectTypeChange,
handleDeviceChange,
handleWidgetEditorToggle,
});

const isStory = tab === "story";

return (
<DndProvider>
<Wrapper>
Expand All @@ -85,7 +88,7 @@ const Editor: React.FC<Props> = ({ sceneId, projectId, workspaceId, tab, stories
<Center>
{secondaryNavbar}
<CenterContents hasNav={!!secondaryNavbar}>
{isStory && (
{selectedProjectType === "story" && (
<StoryPanel
selectedStory={selectedStory}
selectedPage={selectedPage}
Expand Down
121 changes: 121 additions & 0 deletions web/src/beta/features/Editor/tabs/publish/Nav/PublishModal/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useState, useEffect, useCallback } from "react";

import { publishingType } from "@reearth/classic/components/molecules/EarthEditor/Header/index";
import generateRandomString from "@reearth/classic/util/generate-random-string";

export type PublishStatus = "published" | "limited" | "unpublished";

export type Validation = "too short" | "not match";
export type CopiedItemKey = {
url?: boolean;
embedCode?: boolean;
};

export default (
publishing?: publishingType,
publishStatus?: PublishStatus,
defaultAlias?: string,
onPublish?: (publishStatus: PublishStatus) => Promise<void>,
onClose?: () => void,
onAliasValidate?: (alias: string) => void,
onCopyToClipBoard?: () => void,
) => {
const [copiedKey, setCopiedKey] = useState<CopiedItemKey>();
const [alias, changeAlias] = useState(defaultAlias);
const [validation, changeValidation] = useState<Validation>();
const [statusChanged, setStatusChange] = useState(false);
const [showOptions, setOptions] = useState(!defaultAlias);
const [searchIndex, setSearchIndex] = useState(false);

useEffect(() => {
setSearchIndex(!!(publishStatus === "published"));
}, [publishStatus]);

const handleSearchIndexChange = useCallback(() => {
setSearchIndex(!searchIndex);
}, [searchIndex]);

const handleCopyToClipBoard = useCallback(
(key: keyof CopiedItemKey, value: string | undefined) =>
(_: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
if (!value) return;
setCopiedKey(prevState => ({
...prevState,
[key]: true,
}));
navigator.clipboard.writeText(value);
onCopyToClipBoard?.();
},
[onCopyToClipBoard],
);

const validate = useCallback(
(a?: string) => {
if (!a) {
changeValidation(undefined);
return;
}
if (a.length < 5) {
changeValidation("too short");
} else if (!/^[A-Za-z0-9_-]*$/.test(a)) {
changeValidation("not match");
} else {
changeValidation(undefined);
onAliasValidate?.(a);
}
},
[onAliasValidate],
);

const onAliasChange = useCallback(
(value?: string) => {
const a = value || generateAlias();
changeAlias(a);
validate(a);
},
[validate], // eslint-disable-line react-hooks/exhaustive-deps
);

const handleClose = useCallback(() => {
onClose?.();
onAliasChange(defaultAlias);
setStatusChange(false);
setOptions(defaultAlias ? false : true);
}, [onClose, defaultAlias]); // eslint-disable-line react-hooks/exhaustive-deps

const generateAlias = useCallback(() => {
const str = generateRandomString(10);
changeAlias(str);
return str;
}, []);

useEffect(() => {
onAliasChange(defaultAlias);
}, [defaultAlias]); // eslint-disable-line react-hooks/exhaustive-deps

const handlePublish = useCallback(async () => {
if (!publishing) return;
const mode =
publishing === "unpublishing" ? "unpublished" : !searchIndex ? "limited" : "published";
await onPublish?.(mode);
if (publishing === "unpublishing") {
handleClose?.();
} else {
setStatusChange(true);
}
}, [onPublish, publishing, searchIndex, setStatusChange, handleClose]);

return {
statusChanged,
alias,
validation,
copiedKey,
showOptions,
searchIndex,
handlePublish,
handleClose,
handleCopyToClipBoard,
handleSearchIndexChange,
setOptions,
};
};
Loading

0 comments on commit 5f31419

Please sign in to comment.