Refactor control components file structure
This commit is contained in:
171
src/components/controls/DrawingToolSettings.tsx
Normal file
171
src/components/controls/DrawingToolSettings.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { useEffect } from "react";
|
||||
import { Flex, IconButton } from "theme-ui";
|
||||
import { useMedia } from "react-media";
|
||||
|
||||
import RadioIconButton from "../RadioIconButton";
|
||||
|
||||
import ColorControl from "./shared/ColorControl";
|
||||
import AlphaBlendToggle from "./shared/AlphaBlendToggle";
|
||||
import ToolSection from "./shared/ToolSection";
|
||||
|
||||
import BrushIcon from "../../icons/BrushToolIcon";
|
||||
import BrushPaintIcon from "../../icons/BrushPaintIcon";
|
||||
import BrushLineIcon from "../../icons/BrushLineIcon";
|
||||
import BrushRectangleIcon from "../../icons/BrushRectangleIcon";
|
||||
import BrushCircleIcon from "../../icons/BrushCircleIcon";
|
||||
import BrushTriangleIcon from "../../icons/BrushTriangleIcon";
|
||||
import EraseAllIcon from "../../icons/EraseAllIcon";
|
||||
import EraseIcon from "../../icons/EraseToolIcon";
|
||||
|
||||
import UndoButton from "./shared/UndoButton";
|
||||
import RedoButton from "./shared/RedoButton";
|
||||
|
||||
import Divider from "../Divider";
|
||||
|
||||
import { useKeyboard } from "../../contexts/KeyboardContext";
|
||||
|
||||
import shortcuts from "../../shortcuts";
|
||||
|
||||
import {
|
||||
DrawingToolSettings as DrawingToolSettingsType,
|
||||
DrawingToolType,
|
||||
} from "../../types/Drawing";
|
||||
|
||||
type DrawingToolSettingsProps = {
|
||||
settings: DrawingToolSettingsType;
|
||||
onSettingChange: (change: Partial<DrawingToolSettingsType>) => void;
|
||||
onToolAction: (action: string) => void;
|
||||
disabledActions: string[];
|
||||
};
|
||||
|
||||
function DrawingToolSettings({
|
||||
settings,
|
||||
onSettingChange,
|
||||
onToolAction,
|
||||
disabledActions,
|
||||
}: DrawingToolSettingsProps) {
|
||||
// Keyboard shotcuts
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (shortcuts.drawBrush(event)) {
|
||||
onSettingChange({ type: "brush" });
|
||||
} else if (shortcuts.drawPaint(event)) {
|
||||
onSettingChange({ type: "paint" });
|
||||
} else if (shortcuts.drawLine(event)) {
|
||||
onSettingChange({ type: "line" });
|
||||
} else if (shortcuts.drawRect(event)) {
|
||||
onSettingChange({ type: "rectangle" });
|
||||
} else if (shortcuts.drawCircle(event)) {
|
||||
onSettingChange({ type: "circle" });
|
||||
} else if (shortcuts.drawTriangle(event)) {
|
||||
onSettingChange({ type: "triangle" });
|
||||
} else if (shortcuts.drawErase(event)) {
|
||||
onSettingChange({ type: "erase" });
|
||||
} else if (shortcuts.drawBlend(event)) {
|
||||
onSettingChange({ useBlending: !settings.useBlending });
|
||||
} else if (shortcuts.redo(event) && !disabledActions.includes("redo")) {
|
||||
onToolAction("mapRedo");
|
||||
} else if (shortcuts.undo(event) && !disabledActions.includes("undo")) {
|
||||
onToolAction("mapUndo");
|
||||
}
|
||||
}
|
||||
useKeyboard(handleKeyDown);
|
||||
|
||||
// Change to brush if on erase and it gets disabled
|
||||
useEffect(() => {
|
||||
if (settings.type === "erase" && disabledActions.includes("erase")) {
|
||||
onSettingChange({ type: "brush" });
|
||||
}
|
||||
}, [disabledActions, settings, onSettingChange]);
|
||||
|
||||
const isSmallScreen = useMedia({ query: "(max-width: 799px)" });
|
||||
|
||||
const tools = [
|
||||
{
|
||||
id: "brush",
|
||||
title: "Brush (B)",
|
||||
isSelected: settings.type === "brush",
|
||||
icon: <BrushIcon />,
|
||||
},
|
||||
{
|
||||
id: "paint",
|
||||
title: "Paint (P)",
|
||||
isSelected: settings.type === "paint",
|
||||
icon: <BrushPaintIcon />,
|
||||
},
|
||||
{
|
||||
id: "line",
|
||||
title: "Line (L)",
|
||||
isSelected: settings.type === "line",
|
||||
icon: <BrushLineIcon />,
|
||||
},
|
||||
{
|
||||
id: "rectangle",
|
||||
title: "Rectangle (R)",
|
||||
isSelected: settings.type === "rectangle",
|
||||
icon: <BrushRectangleIcon />,
|
||||
},
|
||||
{
|
||||
id: "circle",
|
||||
title: "Circle (C)",
|
||||
isSelected: settings.type === "circle",
|
||||
icon: <BrushCircleIcon />,
|
||||
},
|
||||
{
|
||||
id: "triangle",
|
||||
title: "Triangle (T)",
|
||||
isSelected: settings.type === "triangle",
|
||||
icon: <BrushTriangleIcon />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<ColorControl
|
||||
color={settings.color}
|
||||
onColorChange={(color) => onSettingChange({ color })}
|
||||
exclude={["primary"]}
|
||||
/>
|
||||
<Divider vertical />
|
||||
<ToolSection
|
||||
tools={tools}
|
||||
onToolClick={(tool) =>
|
||||
onSettingChange({ type: tool.id as DrawingToolType })
|
||||
}
|
||||
collapse={isSmallScreen}
|
||||
/>
|
||||
<Divider vertical />
|
||||
<RadioIconButton
|
||||
title="Erase (E)"
|
||||
onClick={() => onSettingChange({ type: "erase" })}
|
||||
isSelected={settings.type === "erase"}
|
||||
disabled={disabledActions.includes("erase")}
|
||||
>
|
||||
<EraseIcon />
|
||||
</RadioIconButton>
|
||||
<IconButton
|
||||
aria-label="Erase All"
|
||||
title="Erase All"
|
||||
onClick={() => onToolAction("eraseAll")}
|
||||
disabled={disabledActions.includes("erase")}
|
||||
>
|
||||
<EraseAllIcon />
|
||||
</IconButton>
|
||||
<Divider vertical />
|
||||
<AlphaBlendToggle
|
||||
useBlending={settings.useBlending}
|
||||
onBlendingChange={(useBlending) => onSettingChange({ useBlending })}
|
||||
/>
|
||||
<Divider vertical />
|
||||
<UndoButton
|
||||
onClick={() => onToolAction("mapUndo")}
|
||||
disabled={disabledActions.includes("undo")}
|
||||
/>
|
||||
<RedoButton
|
||||
onClick={() => onToolAction("mapRedo")}
|
||||
disabled={disabledActions.includes("redo")}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default DrawingToolSettings;
|
||||
150
src/components/controls/FogToolSettings.tsx
Normal file
150
src/components/controls/FogToolSettings.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Flex } from "theme-ui";
|
||||
import { useMedia } from "react-media";
|
||||
|
||||
import RadioIconButton from "../RadioIconButton";
|
||||
|
||||
import MultilayerToggle from "./shared/MultilayerToggle";
|
||||
import FogPreviewToggle from "./shared/FogPreviewToggle";
|
||||
import FogCutToggle from "./shared/FogCutToggle";
|
||||
|
||||
import FogBrushIcon from "../../icons/FogBrushIcon";
|
||||
import FogPolygonIcon from "../../icons/FogPolygonIcon";
|
||||
import FogRemoveIcon from "../../icons/FogRemoveIcon";
|
||||
import FogToggleIcon from "../../icons/FogToggleIcon";
|
||||
import FogRectangleIcon from "../../icons/FogRectangleIcon";
|
||||
|
||||
import UndoButton from "./shared/UndoButton";
|
||||
import RedoButton from "./shared/RedoButton";
|
||||
import ToolSection from "./shared/ToolSection";
|
||||
|
||||
import Divider from "../Divider";
|
||||
|
||||
import { useKeyboard } from "../../contexts/KeyboardContext";
|
||||
|
||||
import shortcuts from "../../shortcuts";
|
||||
|
||||
import {
|
||||
FogToolSettings as FogToolSettingsType,
|
||||
FogToolType,
|
||||
} from "../../types/Fog";
|
||||
|
||||
type FogToolSettingsProps = {
|
||||
settings: FogToolSettingsType;
|
||||
onSettingChange: (change: Partial<FogToolSettingsType>) => void;
|
||||
onToolAction: (action: string) => void;
|
||||
disabledActions: string[];
|
||||
};
|
||||
|
||||
function FogToolSettings({
|
||||
settings,
|
||||
onSettingChange,
|
||||
onToolAction,
|
||||
disabledActions,
|
||||
}: FogToolSettingsProps) {
|
||||
// Keyboard shortcuts
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (shortcuts.fogPolygon(event)) {
|
||||
onSettingChange({ type: "polygon" });
|
||||
} else if (shortcuts.fogBrush(event)) {
|
||||
onSettingChange({ type: "brush" });
|
||||
} else if (shortcuts.fogToggle(event)) {
|
||||
onSettingChange({ type: "toggle" });
|
||||
} else if (shortcuts.fogErase(event)) {
|
||||
onSettingChange({ type: "remove" });
|
||||
} else if (shortcuts.fogLayer(event)) {
|
||||
onSettingChange({ multilayer: !settings.multilayer });
|
||||
} else if (shortcuts.fogPreview(event)) {
|
||||
onSettingChange({ preview: !settings.preview });
|
||||
} else if (shortcuts.fogCut(event)) {
|
||||
onSettingChange({ useFogCut: !settings.useFogCut });
|
||||
} else if (shortcuts.fogRectangle(event)) {
|
||||
onSettingChange({ type: "rectangle" });
|
||||
} else if (shortcuts.redo(event) && !disabledActions.includes("redo")) {
|
||||
onToolAction("fogRedo");
|
||||
} else if (shortcuts.undo(event) && !disabledActions.includes("undo")) {
|
||||
onToolAction("fogUndo");
|
||||
}
|
||||
}
|
||||
|
||||
useKeyboard(handleKeyDown);
|
||||
|
||||
const isSmallScreen = useMedia({ query: "(max-width: 799px)" });
|
||||
const drawTools = [
|
||||
{
|
||||
id: "polygon",
|
||||
title: "Fog Polygon (P)",
|
||||
isSelected: settings.type === "polygon",
|
||||
icon: <FogPolygonIcon />,
|
||||
disabled: settings.preview,
|
||||
},
|
||||
{
|
||||
id: "rectangle",
|
||||
title: "Fog Rectangle (R)",
|
||||
isSelected: settings.type === "rectangle",
|
||||
icon: <FogRectangleIcon />,
|
||||
disabled: settings.preview,
|
||||
},
|
||||
{
|
||||
id: "brush",
|
||||
title: "Fog Brush (B)",
|
||||
isSelected: settings.type === "brush",
|
||||
icon: <FogBrushIcon />,
|
||||
disabled: settings.preview,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<ToolSection
|
||||
tools={drawTools}
|
||||
onToolClick={(tool) =>
|
||||
onSettingChange({ type: tool.id as FogToolType })
|
||||
}
|
||||
collapse={isSmallScreen}
|
||||
/>
|
||||
<Divider vertical />
|
||||
<RadioIconButton
|
||||
title="Toggle Fog (T)"
|
||||
onClick={() => onSettingChange({ type: "toggle" })}
|
||||
isSelected={settings.type === "toggle"}
|
||||
disabled={settings.preview}
|
||||
>
|
||||
<FogToggleIcon />
|
||||
</RadioIconButton>
|
||||
<RadioIconButton
|
||||
title="Erase Fog (E)"
|
||||
onClick={() => onSettingChange({ type: "remove" })}
|
||||
isSelected={settings.type === "remove"}
|
||||
disabled={settings.preview}
|
||||
>
|
||||
<FogRemoveIcon />
|
||||
</RadioIconButton>
|
||||
<Divider vertical />
|
||||
<FogCutToggle
|
||||
useFogCut={settings.useFogCut}
|
||||
onFogCutChange={(useFogCut) => onSettingChange({ useFogCut })}
|
||||
disabled={settings.preview}
|
||||
/>
|
||||
<MultilayerToggle
|
||||
multilayer={settings.multilayer}
|
||||
onMultilayerChange={(multilayer) => onSettingChange({ multilayer })}
|
||||
disabled={settings.preview}
|
||||
/>
|
||||
<FogPreviewToggle
|
||||
useFogPreview={settings.preview}
|
||||
onFogPreviewChange={(preview) => onSettingChange({ preview })}
|
||||
/>
|
||||
<Divider vertical />
|
||||
<UndoButton
|
||||
onClick={() => onToolAction("fogUndo")}
|
||||
disabled={disabledActions.includes("undo")}
|
||||
/>
|
||||
<RedoButton
|
||||
onClick={() => onToolAction("fogRedo")}
|
||||
disabled={disabledActions.includes("redo")}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default FogToolSettings;
|
||||
27
src/components/controls/PointerToolSettings.tsx
Normal file
27
src/components/controls/PointerToolSettings.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Flex } from "theme-ui";
|
||||
|
||||
import ColorControl from "./shared/ColorControl";
|
||||
|
||||
import { PointerToolSettings as PointerToolSettingsType } from "../../types/Pointer";
|
||||
|
||||
type PointerToolSettingsProps = {
|
||||
settings: PointerToolSettingsType;
|
||||
onSettingChange: (change: Partial<PointerToolSettingsType>) => void;
|
||||
};
|
||||
|
||||
function PointerToolSettings({
|
||||
settings,
|
||||
onSettingChange,
|
||||
}: PointerToolSettingsProps) {
|
||||
return (
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<ColorControl
|
||||
color={settings.color}
|
||||
onColorChange={(color) => onSettingChange({ color })}
|
||||
exclude={["black", "darkGray", "lightGray", "white", "primary"]}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default PointerToolSettings;
|
||||
63
src/components/controls/SelectToolSettings.tsx
Normal file
63
src/components/controls/SelectToolSettings.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Flex } from "theme-ui";
|
||||
|
||||
import {
|
||||
SelectToolSettings as SelectToolSettingsType,
|
||||
SelectToolType,
|
||||
} from "../../types/Select";
|
||||
|
||||
import { useKeyboard } from "../../contexts/KeyboardContext";
|
||||
|
||||
import ToolSection from "./shared/ToolSection";
|
||||
|
||||
import shortcuts from "../../shortcuts";
|
||||
|
||||
import RectIcon from "../../icons/SelectRectangleIcon";
|
||||
import PathIcon from "../../icons/SelectPathIcon";
|
||||
|
||||
type SelectToolSettingsProps = {
|
||||
settings: SelectToolSettingsType;
|
||||
onSettingChange: (change: Partial<SelectToolSettingsType>) => void;
|
||||
};
|
||||
|
||||
function SelectToolSettings({
|
||||
settings,
|
||||
onSettingChange,
|
||||
}: SelectToolSettingsProps) {
|
||||
// Keyboard shotcuts
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (shortcuts.selectPath(event)) {
|
||||
onSettingChange({ type: "path" });
|
||||
} else if (shortcuts.selectRect(event)) {
|
||||
onSettingChange({ type: "rectangle" });
|
||||
}
|
||||
}
|
||||
useKeyboard(handleKeyDown);
|
||||
|
||||
const tools = [
|
||||
{
|
||||
id: "path",
|
||||
title: "Lasso Selection (L)",
|
||||
isSelected: settings.type === "path",
|
||||
icon: <PathIcon />,
|
||||
},
|
||||
{
|
||||
id: "rectangle",
|
||||
title: "Rectangle Selection (R)",
|
||||
isSelected: settings.type === "rectangle",
|
||||
icon: <RectIcon />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<ToolSection
|
||||
tools={tools}
|
||||
onToolClick={(tool) =>
|
||||
onSettingChange({ type: tool.id as SelectToolType })
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default SelectToolSettings;
|
||||
26
src/components/controls/shared/AlphaBlendToggle.tsx
Normal file
26
src/components/controls/shared/AlphaBlendToggle.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { IconButton } from "theme-ui";
|
||||
|
||||
import BlendOnIcon from "../../../icons/BlendOnIcon";
|
||||
import BlendOffIcon from "../../../icons/BlendOffIcon";
|
||||
|
||||
type AlphaBlendToggleProps = {
|
||||
useBlending: boolean;
|
||||
onBlendingChange: (useBlending: boolean) => void;
|
||||
};
|
||||
|
||||
function AlphaBlendToggle({
|
||||
useBlending,
|
||||
onBlendingChange,
|
||||
}: AlphaBlendToggleProps) {
|
||||
return (
|
||||
<IconButton
|
||||
aria-label={useBlending ? "Disable Blending (O)" : "Enable Blending (O)"}
|
||||
title={useBlending ? "Disable Blending (O)" : "Enable Blending (O)"}
|
||||
onClick={() => onBlendingChange(!useBlending)}
|
||||
>
|
||||
{useBlending ? <BlendOnIcon /> : <BlendOffIcon />}
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default AlphaBlendToggle;
|
||||
125
src/components/controls/shared/ColorControl.tsx
Normal file
125
src/components/controls/shared/ColorControl.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, SxProp } from "theme-ui";
|
||||
|
||||
import colors, { colorOptions, Color } from "../../../helpers/colors";
|
||||
import MapMenu from "../../map/MapMenu";
|
||||
|
||||
type ColorCircleProps = {
|
||||
color: Color;
|
||||
selected: boolean;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
} & SxProp;
|
||||
|
||||
function ColorCircle({ color, selected, onClick, sx }: ColorCircleProps) {
|
||||
return (
|
||||
<Box
|
||||
key={color}
|
||||
sx={{
|
||||
borderRadius: "50%",
|
||||
transform: "scale(0.75)",
|
||||
backgroundColor: colors[color],
|
||||
cursor: "pointer",
|
||||
...sx,
|
||||
}}
|
||||
onClick={onClick}
|
||||
aria-label={`Brush Color ${color}`}
|
||||
>
|
||||
{selected && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
border: "2px solid white",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
type ColorControlProps = {
|
||||
color: Color;
|
||||
onColorChange: (newColor: Color) => void;
|
||||
exclude: Color[];
|
||||
};
|
||||
|
||||
function ColorControl({ color, onColorChange, exclude }: ColorControlProps) {
|
||||
const [showColorMenu, setShowColorMenu] = useState(false);
|
||||
const [colorMenuOptions, setColorMenuOptions] = useState({});
|
||||
|
||||
function handleControlClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||
if (showColorMenu) {
|
||||
setShowColorMenu(false);
|
||||
setColorMenuOptions({});
|
||||
} else {
|
||||
setShowColorMenu(true);
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
setColorMenuOptions({
|
||||
// Align the right of the submenu to the left of the tool and center vertically
|
||||
left: `${rect.left + rect.width / 2}px`,
|
||||
top: `${rect.bottom + 16}px`,
|
||||
style: { transform: "translateX(-50%)" },
|
||||
// Exclude this node from the sub menus auto close
|
||||
excludeNode: event.currentTarget,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const colorMenu = (
|
||||
<MapMenu
|
||||
isOpen={showColorMenu}
|
||||
onRequestClose={() => {
|
||||
setShowColorMenu(false);
|
||||
setColorMenuOptions({});
|
||||
}}
|
||||
{...colorMenuOptions}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "104px",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
p={1}
|
||||
>
|
||||
{colorOptions
|
||||
.filter((color) => !exclude.includes(color))
|
||||
.map((c) => (
|
||||
<ColorCircle
|
||||
key={c}
|
||||
color={c}
|
||||
selected={c === color}
|
||||
onClick={() => {
|
||||
onColorChange(c);
|
||||
setShowColorMenu(false);
|
||||
setColorMenuOptions({});
|
||||
}}
|
||||
sx={{ width: "25%", paddingTop: "25%" }}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</MapMenu>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ColorCircle
|
||||
color={color}
|
||||
selected
|
||||
onClick={handleControlClick}
|
||||
sx={{ width: "24px", height: "24px" }}
|
||||
/>
|
||||
{colorMenu}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ColorControl.defaultProps = {
|
||||
exclude: [],
|
||||
};
|
||||
|
||||
export default ColorControl;
|
||||
31
src/components/controls/shared/FogCutToggle.tsx
Normal file
31
src/components/controls/shared/FogCutToggle.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IconButton } from "theme-ui";
|
||||
|
||||
import CutOnIcon from "../../../icons/FogCutOnIcon";
|
||||
import CutOffIcon from "../../../icons/FogCutOffIcon";
|
||||
|
||||
type FogCutToggleProps = {
|
||||
useFogCut: boolean;
|
||||
onFogCutChange: (useFogCut: boolean) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
function FogCutToggle({
|
||||
useFogCut,
|
||||
onFogCutChange,
|
||||
disabled,
|
||||
}: FogCutToggleProps) {
|
||||
return (
|
||||
<IconButton
|
||||
aria-label={
|
||||
useFogCut ? "Disable Fog Cutting (C)" : "Enable Fog Cutting (C)"
|
||||
}
|
||||
title={useFogCut ? "Disable Fog Cutting (C)" : "Enable Fog Cutting (C)"}
|
||||
onClick={() => onFogCutChange(!useFogCut)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{useFogCut ? <CutOnIcon /> : <CutOffIcon />}
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default FogCutToggle;
|
||||
30
src/components/controls/shared/FogPreviewToggle.tsx
Normal file
30
src/components/controls/shared/FogPreviewToggle.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { IconButton } from "theme-ui";
|
||||
|
||||
import PreviewOnIcon from "../../../icons/FogPreviewOnIcon";
|
||||
import PreviewOffIcon from "../../../icons/FogPreviewOffIcon";
|
||||
|
||||
type FogPreviewToggleProps = {
|
||||
useFogPreview: boolean;
|
||||
onFogPreviewChange: (useFogCut: boolean) => void;
|
||||
};
|
||||
|
||||
function FogPreviewToggle({
|
||||
useFogPreview,
|
||||
onFogPreviewChange,
|
||||
}: FogPreviewToggleProps) {
|
||||
return (
|
||||
<IconButton
|
||||
aria-label={
|
||||
useFogPreview ? "Disable Fog Preview (F)" : "Enable Fog Preview (F)"
|
||||
}
|
||||
title={
|
||||
useFogPreview ? "Disable Fog Preview (F)" : "Enable Fog Preview (F)"
|
||||
}
|
||||
onClick={() => onFogPreviewChange(!useFogPreview)}
|
||||
>
|
||||
{useFogPreview ? <PreviewOnIcon /> : <PreviewOffIcon />}
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default FogPreviewToggle;
|
||||
31
src/components/controls/shared/MultilayerToggle.tsx
Normal file
31
src/components/controls/shared/MultilayerToggle.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IconButton } from "theme-ui";
|
||||
|
||||
import MultilayerOnIcon from "../../../icons/FogMultilayerOnIcon";
|
||||
import MultilayerOffIcon from "../../../icons/FogMultilayerOffIcon";
|
||||
|
||||
type MultilayerToggleProps = {
|
||||
multilayer: boolean;
|
||||
onMultilayerChange: (multilayer: boolean) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
function MultilayerToggle({
|
||||
multilayer,
|
||||
onMultilayerChange,
|
||||
disabled,
|
||||
}: MultilayerToggleProps) {
|
||||
return (
|
||||
<IconButton
|
||||
aria-label={
|
||||
multilayer ? "Disable Multilayer (L)" : "Enable Multilayer (L)"
|
||||
}
|
||||
title={multilayer ? "Disable Multilayer (L)" : "Enable Multilayer (L)"}
|
||||
onClick={() => onMultilayerChange(!multilayer)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{multilayer ? <MultilayerOnIcon /> : <MultilayerOffIcon />}
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default MultilayerToggle;
|
||||
26
src/components/controls/shared/RedoButton.tsx
Normal file
26
src/components/controls/shared/RedoButton.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import { IconButton } from "theme-ui";
|
||||
|
||||
import RedoIcon from "../../../icons/RedoIcon";
|
||||
|
||||
import { isMacLike } from "../../../helpers/shared";
|
||||
|
||||
type RedoButtonProps = {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
function RedoButton({ onClick, disabled }: RedoButtonProps) {
|
||||
return (
|
||||
<IconButton
|
||||
title={`Redo (${isMacLike ? "Cmd" : "Ctrl"} + Shift + Z)`}
|
||||
aria-label={`Redo (${isMacLike ? "Cmd" : "Ctrl"} + Shift + Z)`}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<RedoIcon />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default RedoButton;
|
||||
124
src/components/controls/shared/ToolSection.tsx
Normal file
124
src/components/controls/shared/ToolSection.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Box, Flex } from "theme-ui";
|
||||
|
||||
import RadioIconButton from "../../RadioIconButton";
|
||||
|
||||
export type Tool = {
|
||||
id: string;
|
||||
title: string;
|
||||
isSelected: boolean;
|
||||
icon: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
type ToolSectionProps = {
|
||||
collapse: boolean;
|
||||
tools: Tool[];
|
||||
onToolClick: (tool: Tool) => void;
|
||||
};
|
||||
|
||||
// Section of map tools with the option to collapse into a vertical list
|
||||
function ToolSection({ collapse, tools, onToolClick }: ToolSectionProps) {
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
const [collapsedTool, setCollapsedTool] = useState<Tool>();
|
||||
|
||||
useEffect(() => {
|
||||
const selectedTool = tools.find((tool) => tool.isSelected);
|
||||
if (selectedTool) {
|
||||
setCollapsedTool(selectedTool);
|
||||
} else {
|
||||
// No selected tool, deselect if we have a tool or get the first tool if not
|
||||
setCollapsedTool((prevTool) =>
|
||||
prevTool ? { ...prevTool, isSelected: false } : tools[0]
|
||||
);
|
||||
}
|
||||
}, [tools]);
|
||||
|
||||
function handleToolClick(tool: Tool) {
|
||||
if (collapse && tool.isSelected) {
|
||||
setShowMore(!showMore);
|
||||
} else if (collapse && !tool.isSelected) {
|
||||
setShowMore(false);
|
||||
}
|
||||
onToolClick(tool);
|
||||
}
|
||||
|
||||
function renderTool(tool: Tool) {
|
||||
return (
|
||||
<RadioIconButton
|
||||
title={tool.title}
|
||||
onClick={() => handleToolClick(tool)}
|
||||
key={tool.id}
|
||||
isSelected={tool.isSelected}
|
||||
disabled={tool.disabled}
|
||||
>
|
||||
{tool.icon}
|
||||
</RadioIconButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (collapse) {
|
||||
if (!collapsedTool) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Box sx={{ position: "relative" }}>
|
||||
{renderTool(collapsedTool)}
|
||||
{/* Render chevron when more tools is available */}
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderTop: "4px solid",
|
||||
borderTopColor: "text",
|
||||
borderLeft: "4px solid transparent",
|
||||
borderRight: "4px solid transparent",
|
||||
transform: "translate(0, -4px) rotate(-45deg)",
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
{showMore && (
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "40px",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
flexDirection: "column",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
bg="overlay"
|
||||
p={2}
|
||||
>
|
||||
{tools.filter((tool) => !tool.isSelected).map(renderTool)}
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{tools.map((tool) => (
|
||||
<RadioIconButton
|
||||
title={tool.title}
|
||||
onClick={() => handleToolClick(tool)}
|
||||
key={tool.id}
|
||||
isSelected={tool.isSelected}
|
||||
disabled={tool.disabled}
|
||||
>
|
||||
{tool.icon}
|
||||
</RadioIconButton>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ToolSection.defaultProps = {
|
||||
collapse: false,
|
||||
};
|
||||
|
||||
export default ToolSection;
|
||||
26
src/components/controls/shared/UndoButton.tsx
Normal file
26
src/components/controls/shared/UndoButton.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import { IconButton } from "theme-ui";
|
||||
|
||||
import UndoIcon from "../../../icons/UndoIcon";
|
||||
|
||||
import { isMacLike } from "../../../helpers/shared";
|
||||
|
||||
type UndoButtonProps = {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
function UndoButton({ onClick, disabled }: UndoButtonProps) {
|
||||
return (
|
||||
<IconButton
|
||||
title={`Undo (${isMacLike ? "Cmd" : "Ctrl"} + Z)`}
|
||||
aria-label={`Undo (${isMacLike ? "Cmd" : "Ctrl"} + Z)`}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<UndoIcon />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default UndoButton;
|
||||
Reference in New Issue
Block a user