Skip to content

Commit

Permalink
chore(web): visualizer dashbord (#1023)
Browse files Browse the repository at this point in the history
Co-authored-by: airslice <[email protected]>
  • Loading branch information
mkumbobeaty and airslice authored Jul 2, 2024
1 parent 7d0a6f6 commit b8a4912
Show file tree
Hide file tree
Showing 57 changed files with 2,221 additions and 123 deletions.
14 changes: 7 additions & 7 deletions web/src/beta/features/Assets/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ export default ({
const openDeleteModal = useCallback(() => setDeleteModalVisible(true), []);
const closeDeleteModal = useCallback(() => setDeleteModalVisible(false), []);
const assetsWrapperRef = useRef<HTMLDivElement>(null);
const sortOptions: { key: string; label: string }[] = useMemo(
const sortOptions: { value: string; label: string }[] = useMemo(
() => [
{ key: "date", label: t("Last Uploaded") },
{ key: "date-reverse", label: t("First Uploaded") },
{ key: "name", label: t("A To Z") },
{ key: "name-reverse", label: t("Z To A") },
{ key: "size", label: t("Size Small to Large") },
{ key: "size-reverse", label: t("Size Large to Small") },
{ value: "date", label: t("Last Uploaded") },
{ value: "date-reverse", label: t("First Uploaded") },
{ value: "name", label: t("A To Z") },
{ value: "name-reverse", label: t("Z To A") },
{ value: "size", label: t("Size Small to Large") },
{ value: "size-reverse", label: t("Size Large to Small") },
],
[t],
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { FC } from "react";

import { Asset } from "@reearth/beta/features/Assets/types";
import { Icon, Typography } from "@reearth/beta/lib/reearth-ui";
import { styled, useTheme } from "@reearth/services/theme";

type AssetCardProps = {
asset: Asset;
icon?: "image" | "file" | "assetNoSupport";
isSelected?: boolean;
onAssetSelect?: (assets: Asset[]) => void;
};

const AssetCard: FC<AssetCardProps> = ({ asset, icon, isSelected, onAssetSelect }) => {
const theme = useTheme();

const renderContent = () => {
switch (icon) {
case "image":
return <AssetImage url={asset.url} />;
case "file":
case "assetNoSupport":
return (
<IconWrapper>
<Icon icon="fileFilled" color={theme.content.weak} size="large" />
</IconWrapper>
);
default:
return null;
}
};

return (
<CardWrapper onClick={() => onAssetSelect?.([asset])} isSelected={isSelected}>
{renderContent()}
<AssetName>
<Typography size="body">{asset.name}</Typography>
</AssetName>
</CardWrapper>
);
};

export default AssetCard;

const CardWrapper = styled("div")<{ isSelected?: boolean }>(({ theme, isSelected }) => ({
display: "flex",
flexDirection: "column",
border: `1px solid ${isSelected ? theme.select.main : "transparent"}`,
boxSizing: "border-box",
width: "100%",
height: "160px",
padding: theme.spacing.smallest,
borderRadius: theme.radius.small,
}));

const AssetImage = styled("div")<{ url?: string }>(({ theme, url }) => ({
background: url ? `url(${url}) center/cover` : theme.bg[1],
borderRadius: theme.radius.small,
flex: "3",
}));

const IconWrapper = styled("div")(({ theme }) => ({
display: "flex",
alignItems: "center",
justifyContent: "center",
background: theme.bg[1],
borderRadius: theme.radius.small,
flex: "3",
}));

const AssetName = styled("div")(({ theme }) => ({
paddingTop: theme.spacing.small,
textAlign: "center",
wordBreak: "break-word",
flex: "1",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 2,
overflow: "hidden",
textOverflow: "ellipsis",
}));
126 changes: 126 additions & 0 deletions web/src/beta/features/Dashboard/ContentsContainer/Assets/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { FC, useCallback, useMemo, useState } from "react";

import { FILE_FORMATS, IMAGE_FORMATS } from "@reearth/beta/features/Assets/constants";
import useAssets from "@reearth/beta/features/Assets/hooks";
import useFileUploaderHook from "@reearth/beta/hooks/useAssetUploader/hooks";
import { Loading } from "@reearth/beta/lib/reearth-ui";
import { checkIfFileType } from "@reearth/beta/utils/util";
import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

import CommonHeader from "../CommonHeader";

import AssetCard from "./AssetCard";

const ASSETS_VIEW_STATE_STORAGE_KEY = `reearth-visualizer-dashboard-asset-view-state`;

const Assets: FC<{ workspaceId?: string }> = ({ workspaceId }) => {
const t = useT();

const [viewState, setViewState] = useState(
localStorage.getItem(ASSETS_VIEW_STATE_STORAGE_KEY)
? localStorage.getItem(ASSETS_VIEW_STATE_STORAGE_KEY)
: "grid",
);
const handleViewStateChange = useCallback((newView?: string) => {
if (!newView) return;
localStorage.setItem(ASSETS_VIEW_STATE_STORAGE_KEY, newView);
setViewState(newView);
}, []);

const {
assets,
assetsWrapperRef,
isAssetsLoading: isLoading,
hasMoreAssets,
sortOptions,
selectedAssets,
handleGetMoreAssets,
onScrollToBottom,
handleSortChange,
selectAsset,
} = useAssets({ workspaceId });

const selectedAssetsIds = useMemo(() => selectedAssets.map(a => a.id), [selectedAssets]);

const { handleFileUpload } = useFileUploaderHook({
workspaceId: workspaceId,
});

return (
<Wrapper>
<CommonHeader
viewState={viewState || ""}
title={t("Upload File")}
icon="uploadSimple"
options={sortOptions}
onSortChange={handleSortChange}
onChangeView={handleViewStateChange}
onClick={handleFileUpload}
/>
<AssetsWrapper
ref={assetsWrapperRef}
onScroll={e => !isLoading && hasMoreAssets && onScrollToBottom?.(e, handleGetMoreAssets)}>
<AssetGridWrapper>
<AssetsRow>
{assets?.map(asset => (
<AssetCard
key={asset.id}
asset={asset}
isSelected={selectedAssetsIds.includes(asset.id)}
icon={
checkIfFileType(asset.url, FILE_FORMATS)
? "file"
: checkIfFileType(asset.url, IMAGE_FORMATS)
? "image"
: "assetNoSupport"
}
onAssetSelect={selectAsset}
/>
))}
</AssetsRow>
{isLoading && <Loading />}
</AssetGridWrapper>
</AssetsWrapper>
</Wrapper>
);
};

export default Assets;

const Wrapper = styled("div")(() => ({
display: "flex",
flexDirection: "column",
height: "100%",
boxSizing: "border-box",
}));

const AssetsWrapper = styled("div")(({ theme }) => ({
display: "flex",
maxHeight: "calc(100vh - 76px)",
flexDirection: "column",
overflowY: "auto",
padding: `0 ${theme.spacing.largest}px ${theme.spacing.largest}px ${theme.spacing.largest}px`,
}));

const AssetGridWrapper = styled("div")(({ theme }) => ({
display: "flex",
flexDirection: "column",
gap: theme.spacing.normal,
}));

const AssetsRow = styled("div")(({ theme }) => ({
display: "grid",
gap: theme.spacing.normal,
gridTemplateColumns: "repeat(7, 1fr)",

"@media (max-width: 1200px)": {
gridTemplateColumns: "repeat(5, 1fr)",
},
"@media (max-width: 900px)": {
gridTemplateColumns: "repeat(3, 1fr)",
},
"@media (max-width: 600px)": {
gridTemplateColumns: "repeat(2, 1fr)",
},
}));
86 changes: 86 additions & 0 deletions web/src/beta/features/Dashboard/ContentsContainer/CommonHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { FC, useCallback } from "react";

import { Button, IconName, Selector, Typography } from "@reearth/beta/lib/reearth-ui";
import { useT } from "@reearth/services/i18n";
import { styled, useTheme } from "@reearth/services/theme";

type HeaderProps = {
viewState: string;
title: string;
icon?: IconName;
options?: { value: string; label?: string }[];
appearance?: "primary" | "secondary" | "dangerous" | "simple";
onClick: () => void;
onChangeView?: (v?: string) => void;
onSortChange?: (value?: string) => void;
};

const CommonHeader: FC<HeaderProps> = ({
title,
icon,
viewState,
options,
appearance,
onClick,
onChangeView,
onSortChange,
}) => {
const theme = useTheme();
const t = useT();

const onChange = useCallback(
(value: string | string[]) => {
onSortChange?.(value as string);
},
[onSortChange],
);

return (
<Header>
<Button title={title} icon={icon} appearance={appearance} onClick={onClick} />

<Actions>
<Typography size="body">{t("Sort")}: </Typography>
<SelectorContainer>
<Selector options={options || []} onChange={onChange} />
</SelectorContainer>

<Button
iconButton
icon="grid"
iconColor={viewState === "grid" ? theme.content.main : theme.content.weak}
appearance="simple"
onClick={() => onChangeView?.("grid")}
shadow={false}
size="small"
/>
<Button
iconButton
icon="list"
iconColor={viewState === "list" ? theme.content.main : theme.content.weak}
appearance="simple"
disabled={icon === "uploadSimple" ? true : false}
onClick={() => onChangeView?.("list")}
shadow={false}
size="small"
/>
</Actions>
</Header>
);
};

export default CommonHeader;

const Header = styled("div")(({ theme }) => ({
display: "flex",
justifyContent: "space-between",
padding: theme.spacing.largest,
}));

const Actions = styled("div")(({ theme }) => ({
display: "flex",
alignItems: "center",
gap: theme.spacing.small,
}));

const SelectorContainer = styled("div")(() => ({ minWidth: "130px" }));
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { FC } from "react";

import { Typography } from "@reearth/beta/lib/reearth-ui";
import { styled } from "@reearth/services/theme";

import { Member } from "../../type";

const ListItem: FC<{ member: Member }> = ({ member }) => {
return (
<StyledListItem>
<Avatar>
<Typography size="body">{member.user?.name.charAt(0).toUpperCase()}</Typography>
</Avatar>
<TypographyWrapper>
<Typography size="body">{member.user?.name}</Typography>
</TypographyWrapper>
<TypographyWrapper>
<Typography size="body">{member.user?.email}</Typography>
</TypographyWrapper>
<TypographyWrapper>
<Typography size="body">
{member.role.charAt(0).toUpperCase() + member.role.slice(1).toLowerCase()}
</Typography>
</TypographyWrapper>
</StyledListItem>
);
};

export default ListItem;

const StyledListItem = styled("div")(({ theme }) => ({
display: "grid",
gridTemplateColumns: "auto 1fr 1fr 1fr",
padding: `${theme.spacing.small}px ${theme.spacing.normal}px`,
alignItems: "center",
background: theme.bg[1],
borderRadius: theme.radius.normal,
gap: theme.spacing.small,
}));

const Avatar = styled("div")(({ theme }) => ({
width: "25px",
height: "25px",
borderRadius: "50%",
background: theme.bg[2],
display: "flex",
alignItems: "center",
justifyContent: "center",
}));

const TypographyWrapper = styled("div")(() => ({
overflow: "hidden",
textOverflow: "ellipsis",
}));
Loading

0 comments on commit b8a4912

Please sign in to comment.