Skip to content

Commit

Permalink
feat: optimizing the list view, moving towards new architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
vojtatom committed Dec 2, 2024
1 parent 0b69c7a commit a88c6cd
Show file tree
Hide file tree
Showing 33 changed files with 386 additions and 667 deletions.
18 changes: 2 additions & 16 deletions app/core/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import { Breadcrumbs, Flex, Item, View } from "@adobe/react-spectrum";
import { Flex, View } from "@adobe/react-spectrum";
import { useUser } from "@core/hooks/useUser";

type BreadcrumbItem = {
key: string;
children: string;
link?: string;
};

type HeaderProps = {
nav: BreadcrumbItem[];
children?: React.ReactNode;
};

export default function Header(props: HeaderProps) {
const { nav, children } = props;
const { children } = props;
const user = useUser();

return (
Expand All @@ -27,13 +20,6 @@ export default function Header(props: HeaderProps) {
backgroundColor="gray-50"
>
<Flex direction="row" width="100%" gap="size-10" justifyContent="start" alignItems="center">
<Breadcrumbs marginX="size-100">
{nav.map((item) => (
<Item key={item.key} href={item.link}>
{item.children}
</Item>
))}
</Breadcrumbs>
{children}
</Flex>
</View>
Expand Down
20 changes: 20 additions & 0 deletions app/core/components/SidebarHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Flex, Heading, View } from "@adobe/react-spectrum";
import { ReactNode } from "react";

type SidebarHeaderProps = {
title: string;
children?: ReactNode;
};

export default function SidebarHeader({ children, title }: SidebarHeaderProps) {
return (
<View position="relative" overflow="hidden" marginX="size-200">
<Flex direction="row" width="100%" alignItems="center" justifyContent="space-between" gap="size-100">
<Heading level={4} marginY="size-200">
{title}
</Heading>
{children}
</Flex>
</View>
);
}
5 changes: 5 additions & 0 deletions app/core/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SpectrumToastOptions } from "@react-spectrum/toast";

export const toasterOptions: SpectrumToastOptions = {
timeout: 5000,
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
RadioGroup,
TextField,
} from "@adobe/react-spectrum";
import { toasterOptions } from "@core/defaults";
import { ToastQueue } from "@react-spectrum/toast";
import { useCallback, useState } from "react";

Expand All @@ -17,26 +18,23 @@ type AddValueDialogProps = {
onSubmit: (value: string, type: "string" | "number") => void;
};

export default function AddValueDialog({
close,
onSubmit,
}: AddValueDialogProps) {
export default function AddValueDialog({ close, onSubmit }: AddValueDialogProps) {
const [value, setValue] = useState("");
const [type, setType] = useState<"string" | "number">("string");

const handleSubmit = useCallback(async () => {
if (!value) {
ToastQueue.negative("Value is required");
ToastQueue.negative("Value is required", toasterOptions);
return;
}

try {
onSubmit(value, type);
ToastQueue.positive("Value added successfully");
ToastQueue.positive("Value added successfully", toasterOptions);
close();
} catch (error) {
console.error(error);
ToastQueue.negative("Failed to add value");
ToastQueue.negative("Failed to add value", toasterOptions);
}
}, [value, type, onSubmit, close]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,59 @@ import {
ActionButton,
ActionGroup,
AlertDialog,
ComboBox,
DialogContainer,
DialogTrigger,
Flex,
Item,
Key,
ListView,
Text,
ToggleButton,
Tooltip,
TooltipTrigger,
View,
} from "@adobe/react-spectrum";
import { NoData } from "@core/components/Empty";

import { PositioningContainer } from "@core/components/PositioningContainer";
import SidebarHeader from "@core/components/SidebarHeader";
import { MdiArrowRightBold } from "@core/icons/MdiArrowRightBold";
import { MdiPalette } from "@core/icons/MdiPalette";
import { MdiSortAscending } from "@core/icons/MdiSortAscending";
import { MdiTrash } from "@core/icons/MdiTrash";
import useSelectedSubmodelCount from "@editor/hooks/useSelectedSubmodelCount";
import ColorPaletteDialog from "@features/editor-metadata-color/components/EditorMetadataColorPaletteDialog";
import useMetadataAssignValue from "@features/editor-metadata/hooks/useMetadataAssignValue";
import useMetadataContext from "@features/editor-metadata/hooks/useMetadataContext";
import useMetadataEdits from "@features/editor-metadata/hooks/useMetadataEdits";
import useMetadataSelection from "@features/editor-metadata/hooks/useMetadataSelection";
import useStyles from "@features/editor-metadata/hooks/useStyles";
import { useEditorContext } from "@features/editor/hooks/useEditorContext";
import { useCallback, useMemo, useState } from "react";
import useMetadataContext from "../hooks/useMetadataContext";
import useMetadataEdits from "../hooks/useMetadataEdits";
import useMetadataSelection from "../hooks/useMetadataSelection";
import useStyles from "../hooks/useStyles";
import DebouncedColorPicker from "./DebouncedColorPicker";
import AddValueDialog from "./EditorMetadataAddValueDialog";
import ColorPaletteDialog from "./EditorMetadataColorPaletteDialog";

export default function EditorMetadataValues() {
const { activeMetadataColumn, setActiveMetadataColumn } = useEditorContext();
export default function EditorMetadataColor() {
return (
<PositioningContainer>
<View position="relative" height="100%" overflow="auto" backgroundColor="gray-50">
<EditorMetadataColorList />
</View>
</PositioningContainer>
);
}

function EditorMetadataColorList() {
const { activeMetadataColumn } = useEditorContext();
const selectedCount = useSelectedSubmodelCount();
const { setStyle } = useStyles();

const [addValueDialogOpen, setAddValueDialogOpen] = useState(false);
const [deleteValuesDialogOpen, setDeleteValuesDialogOpen] = useState(false);

const { aggregatedRows, undefinedItems, columns, selectedValueKeys, sort, setSort } = useMetadataContext();
const { aggregatedRows, selectedValueKeys } = useMetadataContext();

const { handleSelection, select } = useMetadataSelection(selectedValueKeys, activeMetadataColumn);
const { assignValue, removeValue } = useMetadataEdits();
const assignValue = useMetadataAssignValue();
const { removeValue } = useMetadataEdits();

const handleItemAction = useCallback(
(key: Key, value: string | number) => {
Expand Down Expand Up @@ -78,48 +88,47 @@ export default function EditorMetadataValues() {

return (
<PositioningContainer>
<Flex direction="column" height="100%" gap="size-100" marginX="size-200">
<View width="100%" marginTop="size-100">
<ComboBox
label="Metadata column"
defaultItems={columns}
width="100%"
onSelectionChange={(key) => setActiveMetadataColumn(key?.toString() || "")}
selectedKey={activeMetadataColumn}
>
{(item) => <Item key={item.key}>{item.key}</Item>}
</ComboBox>
</View>
{undefinedItems && (
<View position="relative" overflow="hidden">
<Flex marginY="size-100" direction="row" width="100%" alignItems="center" gap="size-100">
<Text flex>{undefinedItems?.count} additional items with undefined value</Text>
<DialogTrigger>
<TooltipTrigger delay={0} placement="bottom">
<ActionButton isDisabled={activeMetadataColumn === undefined}>
<MdiPalette />
</ActionButton>
<Tooltip>Assign Color Pallete</Tooltip>
</TooltipTrigger>
{(close) => <ColorPaletteDialog close={close} />}
</DialogTrigger>
<Flex direction="column" height="100%" gap="size-100">
{/* <View position="relative" overflow="hidden" marginTop="size-100">
<Flex marginY="size-100" direction="row" width="100%" alignItems="center" gap="size-100">
<Text flex>{undefinedItems?.count} additional items with undefined value</Text>
<DialogTrigger>
<TooltipTrigger delay={0} placement="bottom">
<ToggleButton aria-label="Sort" isSelected={sort} onPress={() => setSort(!sort)}>
<MdiSortAscending />
</ToggleButton>
<Tooltip>Sort values</Tooltip>
<ActionButton isDisabled={activeMetadataColumn === undefined}>
<MdiPalette />
</ActionButton>
<Tooltip>Assign Color Pallete</Tooltip>
</TooltipTrigger>
</Flex>
</View>
)}
<View position="relative" flex height="100%" overflow="hidden" marginBottom="size-100">
<ActionBarContainer height="100%" width="100%">
{(close) => <ColorPaletteDialog close={close} />}
</DialogTrigger>
<TooltipTrigger delay={0} placement="bottom">
<ToggleButton aria-label="Sort" isSelected={sort} onPress={() => setSort(!sort)}>
<MdiSortAscending />
</ToggleButton>
<Tooltip>Sort values</Tooltip>
</TooltipTrigger>
</Flex>
</View> */}

<SidebarHeader title="Metadata Values in Active Column">
<DialogTrigger>
<TooltipTrigger delay={0} placement="bottom">
<ActionButton isDisabled={activeMetadataColumn === undefined} isQuiet>
<MdiPalette />
</ActionButton>
<Tooltip>Assign Color Pallete</Tooltip>
</TooltipTrigger>
{(close) => <ColorPaletteDialog close={close} />}
</DialogTrigger>
</SidebarHeader>

<View position="relative" overflow="hidden" height="100%">
<ActionBarContainer height="100%">
<ListView
isQuiet
density="spacious"
selectionMode="multiple"
aria-label="Model list"
width="100%"
height="100%"
marginBottom="size-100"
items={aggregatedRows}
selectedKeys={selectedValueKeys}
renderEmptyState={() => <NoData heading="No column selected" />}
Expand All @@ -137,19 +146,13 @@ export default function EditorMetadataValues() {
</Text>
<Text
UNSAFE_style={{
fontFamily: "monospace",
opacity: record.selected > 0 ? 1 : 0.6,
}}
>
[{String(record.selected).padStart(4, "0")} of {String(record.count).padStart(4, "0")}]{" "}
{record.value}
</Text>
<Text
slot="description"
UNSAFE_style={{
opacity: record.selected > 0 ? 1 : 0.6,
}}
>
{typeof record.value} - {record.count} items, {record.selected} selected{" "}
</Text>
<ActionGroup
isQuiet
onAction={(key) => handleItemAction(key, record.value)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Text,
View,
} from "@adobe/react-spectrum";
import { toasterOptions } from "@core/defaults";
import { Style } from "@features/editor/data/types";
import { useEditorContext } from "@features/editor/hooks/useEditorContext";
import { parseColor } from "@react-spectrum/color";
Expand Down Expand Up @@ -48,8 +49,7 @@ export default function ColorPaletteDialog({ close }: AddValueDialogProps) {
const next: Style = { ...prev };

//get colorizer function
const mappingRecord =
colorMaps.find(([name]) => name === value) ?? NoneMapping;
const mappingRecord = colorMaps.find(([name]) => name === value) ?? NoneMapping;
const colorizer = mappingRecord[1];

//get active style column
Expand All @@ -60,11 +60,7 @@ export default function ColorPaletteDialog({ close }: AddValueDialogProps) {
const value = row.value;
//if value is number, apply color based on range
if (typeof value === "number") {
const indicator = clamp(
(value - range.start) / (range.end - range.start),
0,
1,
);
const indicator = clamp((value - range.start) / (range.end - range.start), 0, 1);
const color = colorizer(indicator);
const colorParsed = parseColor(color).toFormat("rgb");
column[value] = {
Expand Down Expand Up @@ -95,11 +91,11 @@ export default function ColorPaletteDialog({ close }: AddValueDialogProps) {
return next;
});

ToastQueue.positive("Color map applied successfully");
ToastQueue.positive("Color map applied successfully", toasterOptions);
close();
} catch (error) {
console.error(error);
ToastQueue.negative("Applying color map failed");
ToastQueue.negative("Applying color map failed", toasterOptions);
}
}, [value, close, setStyles, activeMetadataColumn, aggregatedRows, range]);

Expand All @@ -120,8 +116,7 @@ export default function ColorPaletteDialog({ close }: AddValueDialogProps) {
const mappedGradientCss = useMemo(() => {
if (value === "none") return undefined;
if (stats.numberCount === 0) return undefined;
const mappingRecord =
colorMaps.find(([name]) => name === value) ?? NoneMapping;
const mappingRecord = colorMaps.find(([name]) => name === value) ?? NoneMapping;

const colorizer = mappingRecord[1];
const { min, max } = stats;
Expand All @@ -146,10 +141,7 @@ export default function ColorPaletteDialog({ close }: AddValueDialogProps) {
<Dialog onDismiss={close}>
<Heading>Pick color palette</Heading>
<Content>
<Text>
Select color palette. The selected palette will be applied to the
active data column.
</Text>
<Text>Select color palette. The selected palette will be applied to the active data column.</Text>
<Grid
columns={repeat("auto-fit", "size-600")}
autoRows="size-600"
Expand Down Expand Up @@ -178,9 +170,7 @@ export default function ColorPaletteDialog({ close }: AddValueDialogProps) {
<Heading level={4} marginBottom="size-50">
Number values
</Heading>
<Text>
{stats.numberCount} unique number values found in the column.
</Text>
<Text>{stats.numberCount} unique number values found in the column.</Text>
<View marginTop="size-100">
<RangeSlider
label="Mappable range"
Expand Down Expand Up @@ -221,8 +211,8 @@ export default function ColorPaletteDialog({ close }: AddValueDialogProps) {
String values
</Heading>
<Text>
{stats.stringCount} unique string values found in the column.
Colors will be assigned randomly to each unique string value.
{stats.stringCount} unique string values found in the column. Colors will be assigned randomly to each
unique string value.
</Text>
</View>
)}
Expand All @@ -246,12 +236,7 @@ type ColorMapButtonProps = {
selected: boolean;
};

function ColorMapButton({
name,
onClick,
selected,
background,
}: ColorMapButtonProps) {
function ColorMapButton({ name, onClick, selected, background }: ColorMapButtonProps) {
return (
<button
onClick={onClick}
Expand All @@ -261,9 +246,8 @@ function ColorMapButton({
color: "inherit",
borderRadius: "var(--spectrum-alias-border-radius-regular)",
cursor: "pointer",
boxShadow: selected
? "0 0 0 2px var(--spectrum-global-color-gray-800)"
: "none",
boxShadow: selected ? "0 0 0 2px var(--spectrum-global-color-gray-800)" : "none",
WebkitBoxShadow: selected ? "0 0 0 2px var(--spectrum-global-color-gray-800)" : "none",
background: background,
}}
/>
Expand Down
File renamed without changes.
Loading

0 comments on commit a88c6cd

Please sign in to comment.