chore(ui): delete unused files

This commit is contained in:
psychedelicious 2023-11-13 08:43:27 +11:00
parent 71e298b722
commit 5eaea9dd64
81 changed files with 0 additions and 2780 deletions

View File

@ -1,43 +0,0 @@
import { Box, Flex, Icon } from '@chakra-ui/react';
import { memo } from 'react';
import { FaExclamation } from 'react-icons/fa';
const IAIErrorLoadingImageFallback = () => {
return (
<Box
sx={{
position: 'relative',
height: 'full',
width: 'full',
'::before': {
content: "''",
display: 'block',
pt: '100%',
},
}}
>
<Flex
sx={{
position: 'absolute',
top: 0,
insetInlineStart: 0,
height: 'full',
width: 'full',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 'base',
bg: 'base.100',
color: 'base.500',
_dark: {
color: 'base.700',
bg: 'base.850',
},
}}
>
<Icon as={FaExclamation} boxSize={16} opacity={0.7} />
</Flex>
</Box>
);
};
export default memo(IAIErrorLoadingImageFallback);

View File

@ -1,8 +0,0 @@
import { chakra } from '@chakra-ui/react';
/**
* Chakra-enabled <form />
*/
const IAIForm = chakra.form;
export default IAIForm;

View File

@ -1,15 +0,0 @@
import { FormErrorMessage, FormErrorMessageProps } from '@chakra-ui/react';
import { ReactNode } from 'react';
type IAIFormErrorMessageProps = FormErrorMessageProps & {
children: ReactNode | string;
};
export default function IAIFormErrorMessage(props: IAIFormErrorMessageProps) {
const { children, ...rest } = props;
return (
<FormErrorMessage color="error.400" {...rest}>
{children}
</FormErrorMessage>
);
}

View File

@ -1,15 +0,0 @@
import { FormHelperText, FormHelperTextProps } from '@chakra-ui/react';
import { ReactNode } from 'react';
type IAIFormHelperTextProps = FormHelperTextProps & {
children: ReactNode | string;
};
export default function IAIFormHelperText(props: IAIFormHelperTextProps) {
const { children, ...rest } = props;
return (
<FormHelperText margin={0} color="base.400" {...rest}>
{children}
</FormHelperText>
);
}

View File

@ -1,25 +0,0 @@
import { Flex, useColorMode } from '@chakra-ui/react';
import { ReactElement } from 'react';
import { mode } from 'theme/util/mode';
export function IAIFormItemWrapper({
children,
}: {
children: ReactElement | ReactElement[];
}) {
const { colorMode } = useColorMode();
return (
<Flex
sx={{
flexDirection: 'column',
padding: 4,
rowGap: 4,
borderRadius: 'base',
width: 'full',
bg: mode('base.100', 'base.900')(colorMode),
}}
>
{children}
</Flex>
);
}

View File

@ -1,25 +0,0 @@
import {
Checkbox,
CheckboxProps,
FormControl,
FormControlProps,
FormLabel,
} from '@chakra-ui/react';
import { memo, ReactNode } from 'react';
type IAIFullCheckboxProps = CheckboxProps & {
label: string | ReactNode;
formControlProps?: FormControlProps;
};
const IAIFullCheckbox = (props: IAIFullCheckboxProps) => {
const { label, formControlProps, ...rest } = props;
return (
<FormControl {...formControlProps}>
<FormLabel>{label}</FormLabel>
<Checkbox colorScheme="accent" {...rest} />
</FormControl>
);
};
export default memo(IAIFullCheckbox);

View File

@ -1,23 +0,0 @@
import { Flex, Icon } from '@chakra-ui/react';
import { memo } from 'react';
import { FaImage } from 'react-icons/fa';
const SelectImagePlaceholder = () => {
return (
<Flex
sx={{
w: 'full',
h: 'full',
// bg: 'base.800',
borderRadius: 'base',
alignItems: 'center',
justifyContent: 'center',
aspectRatio: '1/1',
}}
>
<Icon color="base.400" boxSize={32} as={FaImage}></Icon>
</Flex>
);
};
export default memo(SelectImagePlaceholder);

View File

@ -1,24 +0,0 @@
import { useBreakpoint } from '@chakra-ui/react';
export default function useResolution():
| 'mobile'
| 'tablet'
| 'desktop'
| 'unknown' {
const breakpointValue = useBreakpoint();
const mobileResolutions = ['base', 'sm'];
const tabletResolutions = ['md', 'lg'];
const desktopResolutions = ['xl', '2xl'];
if (mobileResolutions.includes(breakpointValue)) {
return 'mobile';
}
if (tabletResolutions.includes(breakpointValue)) {
return 'tablet';
}
if (desktopResolutions.includes(breakpointValue)) {
return 'desktop';
}
return 'unknown';
}

View File

@ -1,7 +0,0 @@
import dateFormat from 'dateformat';
/**
* Get a `now` timestamp with 1s precision, formatted as ISO datetime.
*/
export const getTimestamp = () =>
dateFormat(new Date(), `yyyy-mm-dd'T'HH:MM:ss:lo`);

View File

@ -1,71 +0,0 @@
// TODO: Restore variations
// Support code from v2.3 in here.
// export const stringToSeedWeights = (
// string: string
// ): InvokeAI.SeedWeights | boolean => {
// const stringPairs = string.split(',');
// const arrPairs = stringPairs.map((p) => p.split(':'));
// const pairs = arrPairs.map((p: Array<string>): InvokeAI.SeedWeightPair => {
// return { seed: Number(p[0]), weight: Number(p[1]) };
// });
// if (!validateSeedWeights(pairs)) {
// return false;
// }
// return pairs;
// };
// export const validateSeedWeights = (
// seedWeights: InvokeAI.SeedWeights | string
// ): boolean => {
// return typeof seedWeights === 'string'
// ? Boolean(stringToSeedWeights(seedWeights))
// : Boolean(
// seedWeights.length &&
// !seedWeights.some((pair: InvokeAI.SeedWeightPair) => {
// const { seed, weight } = pair;
// const isSeedValid = !isNaN(parseInt(seed.toString(), 10));
// const isWeightValid =
// !isNaN(parseInt(weight.toString(), 10)) &&
// weight >= 0 &&
// weight <= 1;
// return !(isSeedValid && isWeightValid);
// })
// );
// };
// export const seedWeightsToString = (
// seedWeights: InvokeAI.SeedWeights
// ): string => {
// return seedWeights.reduce((acc, pair, i, arr) => {
// const { seed, weight } = pair;
// acc += `${seed}:${weight}`;
// if (i !== arr.length - 1) {
// acc += ',';
// }
// return acc;
// }, '');
// };
// export const seedWeightsToArray = (
// seedWeights: InvokeAI.SeedWeights
// ): Array<Array<number>> => {
// return seedWeights.map((pair: InvokeAI.SeedWeightPair) => [
// pair.seed,
// pair.weight,
// ]);
// };
// export const stringToSeedWeightsArray = (
// string: string
// ): Array<Array<number>> => {
// const stringPairs = string.split(',');
// const arrPairs = stringPairs.map((p) => p.split(':'));
// return arrPairs.map(
// (p: Array<string>): Array<number> => [parseInt(p[0], 10), parseFloat(p[1])]
// );
// };
export default {};

View File

@ -1,16 +0,0 @@
import Konva from 'konva';
import { IRect } from 'konva/lib/types';
/**
* Converts a Konva node to a dataURL
* @param node - The Konva node to convert to a dataURL
* @param boundingBox - The bounding box to crop to
* @returns A dataURL of the node cropped to the bounding box
*/
export const konvaNodeToDataURL = (
node: Konva.Node,
boundingBox: IRect
): string => {
// get a dataURL of the bbox'd region
return node.toDataURL(boundingBox);
};

View File

@ -1,36 +0,0 @@
import { useAppDispatch } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
import { memo, useCallback } from 'react';
import { useControlAdapterControlImage } from '../hooks/useControlAdapterControlImage';
import { controlAdapterImageProcessed } from '../store/actions';
type Props = {
id: string;
};
const ControlAdapterPreprocessButton = ({ id }: Props) => {
const controlImage = useControlAdapterControlImage(id);
const dispatch = useAppDispatch();
const isReady = useIsReadyToEnqueue();
const handleProcess = useCallback(() => {
dispatch(
controlAdapterImageProcessed({
id,
})
);
}, [id, dispatch]);
return (
<IAIButton
size="sm"
onClick={handleProcess}
isDisabled={Boolean(!controlImage) || !isReady}
>
Preprocess
</IAIButton>
);
};
export default memo(ControlAdapterPreprocessButton);

View File

@ -1,108 +0,0 @@
import { As, Badge, Flex } from '@chakra-ui/react';
import IAIDroppable from 'common/components/IAIDroppable';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { TypesafeDroppableData } from 'features/dnd/types';
import { BoardId } from 'features/gallery/store/types';
import { ReactNode, memo } from 'react';
import BoardContextMenu from '../BoardContextMenu';
type GenericBoardProps = {
board_id: BoardId;
droppableData?: TypesafeDroppableData;
onClick: () => void;
isSelected: boolean;
icon: As;
label: string;
dropLabel?: ReactNode;
badgeCount?: number;
};
export const formatBadgeCount = (count: number) =>
Intl.NumberFormat('en-US', {
notation: 'compact',
maximumFractionDigits: 1,
}).format(count);
const GenericBoard = (props: GenericBoardProps) => {
const {
board_id,
droppableData,
onClick,
isSelected,
icon,
label,
badgeCount,
dropLabel,
} = props;
return (
<BoardContextMenu board_id={board_id}>
{(ref) => (
<Flex
ref={ref}
sx={{
flexDir: 'column',
justifyContent: 'space-between',
alignItems: 'center',
cursor: 'pointer',
w: 'full',
h: 'full',
borderRadius: 'base',
}}
>
<Flex
onClick={onClick}
sx={{
position: 'relative',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 'base',
w: 'full',
aspectRatio: '1/1',
overflow: 'hidden',
shadow: isSelected ? 'selected.light' : undefined,
_dark: { shadow: isSelected ? 'selected.dark' : undefined },
flexShrink: 0,
}}
>
<IAINoContentFallback
boxSize={8}
icon={icon}
sx={{
border: '2px solid var(--invokeai-colors-base-200)',
_dark: { border: '2px solid var(--invokeai-colors-base-800)' },
}}
/>
<Flex
sx={{
position: 'absolute',
insetInlineEnd: 0,
top: 0,
p: 1,
}}
>
{badgeCount !== undefined && (
<Badge variant="solid">{formatBadgeCount(badgeCount)}</Badge>
)}
</Flex>
<IAIDroppable data={droppableData} dropLabel={dropLabel} />
</Flex>
<Flex
sx={{
h: 'full',
alignItems: 'center',
fontWeight: isSelected ? 600 : undefined,
fontSize: 'sm',
color: isSelected ? 'base.900' : 'base.700',
_dark: { color: isSelected ? 'base.50' : 'base.200' },
}}
>
{label}
</Flex>
</Flex>
)}
</BoardContextMenu>
);
};
export default memo(GenericBoard);

View File

@ -1,53 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIButton from 'common/components/IAIButton';
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo } from 'react';
import { useBoardName } from 'services/api/hooks/useBoardName';
type Props = {
board_id: 'images' | 'assets' | 'no_board';
};
const SystemBoardButton = ({ board_id }: Props) => {
const dispatch = useAppDispatch();
const selector = useMemo(
() =>
createSelector(
[stateSelector],
({ gallery }) => {
const { selectedBoardId } = gallery;
return { isSelected: selectedBoardId === board_id };
},
defaultSelectorOptions
),
[board_id]
);
const { isSelected } = useAppSelector(selector);
const boardName = useBoardName(board_id);
const handleClick = useCallback(() => {
dispatch(boardIdSelected({ boardId: board_id }));
}, [board_id, dispatch]);
return (
<IAIButton
onClick={handleClick}
size="sm"
isChecked={isSelected}
sx={{
flexGrow: 1,
borderRadius: 'base',
}}
>
{boardName}
</IAIButton>
);
};
export default memo(SystemBoardButton);

View File

@ -1,22 +0,0 @@
import { Flex } from '@chakra-ui/react';
import { memo } from 'react';
import { FaEyeSlash } from 'react-icons/fa';
const CurrentImageHidden = () => {
return (
<Flex
sx={{
w: 'full',
h: 'full',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
color: 'base.400',
}}
>
<FaEyeSlash fontSize="25vh" />
</Flex>
);
};
export default memo(CurrentImageHidden);

View File

@ -1,27 +0,0 @@
import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react';
import { memo } from 'react';
type ImageFallbackSpinnerProps = SpinnerProps;
const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => {
const { size = 'xl', ...rest } = props;
return (
<Flex
sx={{
w: 'full',
h: 'full',
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
color: 'base.400',
minH: 36,
minW: 36,
}}
>
<Spinner size={size} {...rest} />
</Flex>
);
};
export default memo(ImageFallbackSpinner);

View File

@ -1,14 +0,0 @@
import {
ClipInputFieldTemplate,
ClipInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const ClipInputFieldComponent = (
_props: FieldComponentProps<ClipInputFieldValue, ClipInputFieldTemplate>
) => {
return null;
};
export default memo(ClipInputFieldComponent);

View File

@ -1,17 +0,0 @@
import {
CollectionInputFieldTemplate,
CollectionInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const CollectionInputFieldComponent = (
_props: FieldComponentProps<
CollectionInputFieldValue,
CollectionInputFieldTemplate
>
) => {
return null;
};
export default memo(CollectionInputFieldComponent);

View File

@ -1,17 +0,0 @@
import {
CollectionItemInputFieldTemplate,
CollectionItemInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const CollectionItemInputFieldComponent = (
_props: FieldComponentProps<
CollectionItemInputFieldValue,
CollectionItemInputFieldTemplate
>
) => {
return null;
};
export default memo(CollectionItemInputFieldComponent);

View File

@ -1,17 +0,0 @@
import {
ConditioningInputFieldTemplate,
ConditioningInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const ConditioningInputFieldComponent = (
_props: FieldComponentProps<
ConditioningInputFieldValue,
ConditioningInputFieldTemplate
>
) => {
return null;
};
export default memo(ConditioningInputFieldComponent);

View File

@ -1,19 +0,0 @@
import {
ControlInputFieldTemplate,
ControlInputFieldValue,
ControlPolymorphicInputFieldTemplate,
ControlPolymorphicInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const ControlInputFieldComponent = (
_props: FieldComponentProps<
ControlInputFieldValue | ControlPolymorphicInputFieldValue,
ControlInputFieldTemplate | ControlPolymorphicInputFieldTemplate
>
) => {
return null;
};
export default memo(ControlInputFieldComponent);

View File

@ -1,17 +0,0 @@
import {
DenoiseMaskInputFieldTemplate,
DenoiseMaskInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const DenoiseMaskInputFieldComponent = (
_props: FieldComponentProps<
DenoiseMaskInputFieldValue,
DenoiseMaskInputFieldTemplate
>
) => {
return null;
};
export default memo(DenoiseMaskInputFieldComponent);

View File

@ -1,19 +0,0 @@
import {
IPAdapterInputFieldTemplate,
IPAdapterInputFieldValue,
FieldComponentProps,
IPAdapterPolymorphicInputFieldValue,
IPAdapterPolymorphicInputFieldTemplate,
} from 'features/nodes/types/types';
import { memo } from 'react';
const IPAdapterInputFieldComponent = (
_props: FieldComponentProps<
IPAdapterInputFieldValue | IPAdapterPolymorphicInputFieldValue,
IPAdapterInputFieldTemplate | IPAdapterPolymorphicInputFieldTemplate
>
) => {
return null;
};
export default memo(IPAdapterInputFieldComponent);

View File

@ -1,94 +0,0 @@
import {
ImageCollectionInputFieldTemplate,
ImageCollectionInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
import { Flex } from '@chakra-ui/react';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIDropOverlay from 'common/components/IAIDropOverlay';
import { useDroppableTypesafe } from 'features/dnd/hooks/typesafeHooks';
import { NodesMultiImageDropData } from 'features/dnd/types';
import { isValidDrop } from 'features/dnd/util/isValidDrop';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
const ImageCollectionInputFieldComponent = (
props: FieldComponentProps<
ImageCollectionInputFieldValue,
ImageCollectionInputFieldTemplate
>
) => {
const { nodeId, field } = props;
// const dispatch = useAppDispatch();
// const handleDrop = useCallback(
// ({ image_name }: ImageDTO) => {
// dispatch(
// fieldValueChanged({
// nodeId,
// fieldName: field.name,
// value: uniqBy([...(field.value ?? []), { image_name }], 'image_name'),
// })
// );
// },
// [dispatch, field.name, field.value, nodeId]
// );
const droppableData: NodesMultiImageDropData = {
id: `node-${nodeId}-${field.name}`,
actionType: 'SET_MULTI_NODES_IMAGE',
context: { nodeId: nodeId, fieldName: field.name },
};
const {
isOver,
setNodeRef: setDroppableRef,
active,
} = useDroppableTypesafe({
id: `node_${nodeId}`,
data: droppableData,
});
// const handleReset = useCallback(() => {
// dispatch(
// fieldValueChanged({
// nodeId,
// fieldName: field.name,
// value: undefined,
// })
// );
// }, [dispatch, field.name, nodeId]);
return (
<Flex
ref={setDroppableRef}
sx={{
w: 'full',
h: 'full',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
minH: '10rem',
}}
>
{field.value?.map(({ image_name }) => (
<ImageSubField key={image_name} imageName={image_name} />
))}
{isValidDrop(droppableData, active) && <IAIDropOverlay isOver={isOver} />}
</Flex>
);
};
export default memo(ImageCollectionInputFieldComponent);
type ImageSubFieldProps = { imageName: string };
const ImageSubField = (props: ImageSubFieldProps) => {
const { currentData: image } = useGetImageDTOQuery(props.imageName);
return (
<IAIDndImage imageDTO={image} isDropDisabled={true} isDragDisabled={true} />
);
};

View File

@ -1,19 +0,0 @@
import {
LatentsInputFieldTemplate,
LatentsInputFieldValue,
FieldComponentProps,
LatentsPolymorphicInputFieldValue,
LatentsPolymorphicInputFieldTemplate,
} from 'features/nodes/types/types';
import { memo } from 'react';
const LatentsInputFieldComponent = (
_props: FieldComponentProps<
LatentsInputFieldValue | LatentsPolymorphicInputFieldValue,
LatentsInputFieldTemplate | LatentsPolymorphicInputFieldTemplate
>
) => {
return null;
};
export default memo(LatentsInputFieldComponent);

View File

@ -1,19 +0,0 @@
import {
T2IAdapterInputFieldTemplate,
T2IAdapterInputFieldValue,
T2IAdapterPolymorphicInputFieldTemplate,
T2IAdapterPolymorphicInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const T2IAdapterInputFieldComponent = (
_props: FieldComponentProps<
T2IAdapterInputFieldValue | T2IAdapterPolymorphicInputFieldValue,
T2IAdapterInputFieldTemplate | T2IAdapterPolymorphicInputFieldTemplate
>
) => {
return null;
};
export default memo(T2IAdapterInputFieldComponent);

View File

@ -1,14 +0,0 @@
import {
UNetInputFieldTemplate,
UNetInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const UNetInputFieldComponent = (
_props: FieldComponentProps<UNetInputFieldValue, UNetInputFieldTemplate>
) => {
return null;
};
export default memo(UNetInputFieldComponent);

View File

@ -1,14 +0,0 @@
import {
VaeInputFieldTemplate,
VaeInputFieldValue,
FieldComponentProps,
} from 'features/nodes/types/types';
import { memo } from 'react';
const VaeInputFieldComponent = (
_props: FieldComponentProps<VaeInputFieldValue, VaeInputFieldTemplate>
) => {
return null;
};
export default memo(VaeInputFieldComponent);

View File

@ -1,27 +0,0 @@
import { NODE_MIN_WIDTH } from 'features/nodes/types/constants';
import { memo } from 'react';
import { NodeResizeControl, NodeResizerProps } from 'reactflow';
// this causes https://github.com/invoke-ai/InvokeAI/issues/4140
// not using it for now
const NodeResizer = (props: NodeResizerProps) => {
const { ...rest } = props;
return (
<NodeResizeControl
style={{
position: 'absolute',
border: 'none',
background: 'transparent',
width: 15,
height: 15,
bottom: 0,
right: 0,
}}
minWidth={NODE_MIN_WIDTH}
{...rest}
></NodeResizeControl>
);
};
export default memo(NodeResizer);

View File

@ -1,78 +0,0 @@
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { InvocationTemplate, NodeData } from 'features/nodes/types/types';
import { memo } from 'react';
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
import NodeTitle from '../../flow/nodes/common/NodeTitle';
import ScrollableContent from '../ScrollableContent';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
const lastSelectedNodeTemplate = lastSelectedNode
? nodes.nodeTemplates[lastSelectedNode.data.type]
: undefined;
return {
data: lastSelectedNode?.data,
template: lastSelectedNodeTemplate,
};
},
defaultSelectorOptions
);
const InspectorDetailsTab = () => {
const { data, template } = useAppSelector(selector);
const { t } = useTranslation();
if (!template || !data) {
return (
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
);
}
return <Content data={data} template={template} />;
};
export default memo(InspectorDetailsTab);
const Content = (props: { data: NodeData; template: InvocationTemplate }) => {
const { data } = props;
return (
<Box
sx={{
position: 'relative',
w: 'full',
h: 'full',
}}
>
<ScrollableContent>
<Flex
sx={{
flexDir: 'column',
position: 'relative',
p: 1,
gap: 2,
w: 'full',
}}
>
<NodeTitle nodeId={data.id} />
<NotesTextarea nodeId={data.id} />
</Flex>
</ScrollableContent>
</Box>
);
};

View File

@ -1,51 +0,0 @@
import { Box, Text } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAITextarea from 'common/components/IAITextarea';
import { workflowNotesChanged } from 'features/nodes/store/nodesSlice';
import { ChangeEvent, memo, useCallback } from 'react';
const selector = createSelector(stateSelector, ({ nodes }) => {
const { notes } = nodes.workflow;
return {
notes,
};
});
const WorkflowNotesTab = () => {
const { notes } = useAppSelector(selector);
const dispatch = useAppDispatch();
const handleChangeNotes = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => {
dispatch(workflowNotesChanged(e.target.value));
},
[dispatch]
);
return (
<Box sx={{ pos: 'relative', h: 'full' }}>
<IAITextarea
onChange={handleChangeNotes}
value={notes}
fontSize="sm"
sx={{ h: 'full', resize: 'none' }}
/>
<Box sx={{ pos: 'absolute', bottom: 2, right: 2 }}>
<Text
sx={{
fontSize: 'xs',
opacity: 0.5,
userSelect: 'none',
}}
>
{notes.length}
</Text>
</Box>
</Box>
);
};
export default memo(WorkflowNotesTab);

View File

@ -1,11 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { AnyInvocationType } from 'services/events/types';
export const makeTemplateSelector = (type: AnyInvocationType) =>
createSelector(
stateSelector,
({ nodes }) => nodes.nodeTemplates[type],
defaultSelectorOptions
);

View File

@ -1,21 +0,0 @@
import { Flex } from '@chakra-ui/react';
import IAICollapse from 'common/components/IAICollapse';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
const ParamBoundingBoxCollapse = () => {
const { t } = useTranslation();
return (
<IAICollapse label={t('parameters.boundingBoxHeader')}>
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<ParamBoundingBoxWidth />
<ParamBoundingBoxHeight />
</Flex>
</IAICollapse>
);
};
export default memo(ParamBoundingBoxCollapse);

View File

@ -1,39 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISlider from 'common/components/IAISlider';
import { setThreshold } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
(state) => {
const { threshold } = state.generation;
return {
threshold,
};
},
defaultSelectorOptions
);
export default function ParamNoiseThreshold() {
const dispatch = useAppDispatch();
const { threshold } = useAppSelector(selector);
const { t } = useTranslation();
return (
<IAISlider
label={t('parameters.noiseThreshold')}
min={0}
max={20}
step={0.1}
onChange={(v) => dispatch(setThreshold(v))}
handleReset={() => dispatch(setThreshold(0))}
value={threshold}
withInput
withReset
withSliderMarks
/>
);
}

View File

@ -1,39 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISlider from 'common/components/IAISlider';
import { setPerlin } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
(state) => {
const { perlin } = state.generation;
return {
perlin,
};
},
defaultSelectorOptions
);
export default function ParamPerlinNoise() {
const dispatch = useAppDispatch();
const { perlin } = useAppSelector(selector);
const { t } = useTranslation();
return (
<IAISlider
label={t('parameters.perlinNoise')}
min={0}
max={1}
step={0.05}
onChange={(v) => dispatch(setPerlin(v))}
handleReset={() => dispatch(setPerlin(0))}
value={perlin}
withInput
withReset
withSliderMarks
/>
);
}

View File

@ -1,35 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setVariationAmount } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next';
export default function ParamVariationAmount() {
const variationAmount = useAppSelector(
(state: RootState) => state.generation.variationAmount
);
const shouldGenerateVariations = useAppSelector(
(state: RootState) => state.generation.shouldGenerateVariations
);
const { t } = useTranslation();
const dispatch = useAppDispatch();
return (
<IAISlider
label={t('parameters.variationAmount')}
value={variationAmount}
step={0.01}
min={0}
max={1}
isDisabled={!shouldGenerateVariations}
onChange={(v) => dispatch(setVariationAmount(v))}
handleReset={() => dispatch(setVariationAmount(0.1))}
withInput
withReset
withSliderMarks
/>
);
}

View File

@ -1,51 +0,0 @@
// TODO: variations
// import { Flex } from '@chakra-ui/react';
// import { createSelector } from '@reduxjs/toolkit';
// import { stateSelector } from 'app/store/store';
// import { useAppSelector } from 'app/store/storeHooks';
// import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
// import IAICollapse from 'common/components/IAICollapse';
// import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
// import { memo } from 'react';
// import { useTranslation } from 'react-i18next';
// import ParamVariationAmount from './ParamVariationAmount';
// import { ParamVariationToggle } from './ParamVariationToggle';
// import ParamVariationWeights from './ParamVariationWeights';
// const selector = createSelector(
// stateSelector,
// (state) => {
// const activeLabel = state.generation.shouldGenerateVariations
// ? 'Enabled'
// : undefined;
// return { activeLabel };
// },
// defaultSelectorOptions
// );
// const ParamVariationCollapse = () => {
// const { t } = useTranslation();
// const { activeLabel } = useAppSelector(selector);
// const isVariationEnabled = useFeatureStatus('variation').isFeatureEnabled;
// if (!isVariationEnabled) {
// return null;
// }
// return (
// <IAICollapse label={t('parameters.variations')} activeLabel={activeLabel}>
// <Flex sx={{ gap: 2, flexDirection: 'column' }}>
// <ParamVariationToggle />
// <ParamVariationAmount />
// <ParamVariationWeights />
// </Flex>
// </IAICollapse>
// );
// };
// export default memo(ParamVariationCollapse);
export default {};

View File

@ -1,24 +0,0 @@
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch';
import { setShouldGenerateVariations } from 'features/parameters/store/generationSlice';
import { ChangeEvent } from 'react';
export const ParamVariationToggle = () => {
const dispatch = useAppDispatch();
const shouldGenerateVariations = useAppSelector(
(state: RootState) => state.generation.shouldGenerateVariations
);
const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldGenerateVariations(e.target.checked));
return (
<IAISwitch
label="Enable Variations"
isChecked={shouldGenerateVariations}
onChange={handleChange}
/>
);
};

View File

@ -1,41 +0,0 @@
// TODO: variations
// import { RootState } from 'app/store/store';
// import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
// import IAIInput from 'common/components/IAIInput';
// import { validateSeedWeights } from 'common/util/seedWeightPairs';
// import { setSeedWeights } from 'features/parameters/store/generationSlice';
// import { ChangeEvent } from 'react';
// import { useTranslation } from 'react-i18next';
// export default function ParamVariationWeights() {
// const seedWeights = useAppSelector(
// (state: RootState) => state.generation.seedWeights
// );
// const shouldGenerateVariations = useAppSelector(
// (state: RootState) => state.generation.shouldGenerateVariations
// );
// const { t } = useTranslation();
// const dispatch = useAppDispatch();
// const handleChangeSeedWeights = (e: ChangeEvent<HTMLInputElement>) =>
// dispatch(setSeedWeights(e.target.value));
// return (
// <IAIInput
// label={t('parameters.seedWeights')}
// value={seedWeights}
// isInvalid={
// shouldGenerateVariations &&
// !(validateSeedWeights(seedWeights) || seedWeights === '')
// }
// isDisabled={!shouldGenerateVariations}
// onChange={handleChangeSeedWeights}
// />
// );
// }
export default {};

View File

@ -1,4 +0,0 @@
import { RootState } from 'app/store/store';
export const postprocessingSelector = (state: RootState) =>
state.postprocessing;

View File

@ -1,18 +0,0 @@
import { memo } from 'react';
import QueueItemCard from './common/QueueItemCard';
import { useGetCurrentQueueItemQuery } from 'services/api/endpoints/queue';
import { useTranslation } from 'react-i18next';
const CurrentQueueItemCard = () => {
const { t } = useTranslation();
const { data: currentQueueItemData } = useGetCurrentQueueItemQuery();
return (
<QueueItemCard
label={t('queue.current')}
session_queue_item={currentQueueItemData}
/>
);
};
export default memo(CurrentQueueItemCard);

View File

@ -1,18 +0,0 @@
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetNextQueueItemQuery } from 'services/api/endpoints/queue';
import QueueItemCard from './common/QueueItemCard';
const NextQueueItemCard = () => {
const { t } = useTranslation();
const { data: nextQueueItemData } = useGetNextQueueItemQuery();
return (
<QueueItemCard
label={t('queue.next')}
session_queue_item={nextQueueItemData}
/>
);
};
export default memo(NextQueueItemCard);

View File

@ -1,41 +0,0 @@
import { Flex, Skeleton } from '@chakra-ui/react';
import { memo } from 'react';
import { COLUMN_WIDTHS } from './constants';
const QueueItemSkeleton = () => {
return (
<Flex alignItems="center" p={1.5} gap={4} minH={9} h="full" w="full">
<Flex
w={COLUMN_WIDTHS.number}
justifyContent="flex-end"
alignItems="center"
>
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
<Flex w={COLUMN_WIDTHS.statusBadge} alignItems="center">
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
<Flex w={COLUMN_WIDTHS.time} alignItems="center">
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
<Flex w={COLUMN_WIDTHS.batchId} alignItems="center">
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
<Flex w={COLUMN_WIDTHS.fieldValues} alignItems="center" flexGrow={1}>
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
</Flex>
);
};
export default memo(QueueItemSkeleton);

View File

@ -1,54 +0,0 @@
import { Flex, Heading, Text } from '@chakra-ui/react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
const QueueStatusCard = () => {
const { t } = useTranslation();
const { data: queueStatus } = useGetQueueStatusQuery();
return (
<Flex
layerStyle="second"
borderRadius="base"
p={2}
flexDir="column"
gap={1}
w={96}
>
<Heading size="md">{t('queue.status')}</Heading>
<Text>
<Text as="span" fontWeight={600}>
{t('queue.pending')}:{' '}
</Text>
{queueStatus?.queue.pending}
</Text>
<Text>
<Text as="span" fontWeight={600}>
{t('queue.in_progress')}:{' '}
</Text>
{queueStatus?.queue.in_progress}
</Text>
<Text>
<Text as="span" fontWeight={600}>
{t('queue.completed')}:{' '}
</Text>
{queueStatus?.queue.completed}
</Text>
<Text>
<Text as="span" fontWeight={600}>
{t('queue.failed')}:{' '}
</Text>
{queueStatus?.queue.failed}
</Text>
<Text>
<Text as="span" fontWeight={600}>
{t('queue.canceled')}:{' '}
</Text>
{queueStatus?.queue.canceled}
</Text>
</Flex>
);
};
export default memo(QueueStatusCard);

View File

@ -1,52 +0,0 @@
import { Flex, Heading, Text, Tooltip } from '@chakra-ui/react';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import { memo } from 'react';
import { components } from 'services/api/schema';
const QueueItemCard = ({
session_queue_item,
label,
}: {
session_queue_item?: components['schemas']['SessionQueueItem'] | null;
label: string;
}) => {
return (
<Flex
layerStyle="second"
borderRadius="base"
w="full"
p={2}
flexDir="column"
gap={1}
>
<Flex justifyContent="space-between" alignItems="flex-start">
<Heading size="md">{label}</Heading>
{session_queue_item && (
<Tooltip label="Batch ID" placement="top" hasArrow>
<Text fontSize="xs">{session_queue_item.batch_id}</Text>
</Tooltip>
)}
</Flex>
{session_queue_item && (
<ScrollableContent>
<Text>Batch Values: </Text>
{session_queue_item.field_values &&
session_queue_item.field_values
.filter((v) => v.node_path !== 'metadata_accumulator')
.map(({ node_path, field_name, value }) => (
<Text
key={`${session_queue_item.item_id}.${node_path}.${field_name}.${value}`}
>
<Text as="span" fontWeight={600}>
{node_path}.{field_name}
</Text>
: {value}
</Text>
))}
</ScrollableContent>
)}
</Flex>
);
};
export default memo(QueueItemCard);

View File

@ -1,28 +0,0 @@
import { Box } from '@chakra-ui/react';
import { memo, useMemo } from 'react';
import { SessionQueueItemStatus } from 'services/api/endpoints/queue';
const STATUSES = {
pending: { colorScheme: 'cyan', translationKey: 'queue.pending' },
in_progress: { colorScheme: 'yellow', translationKey: 'queue.in_progress' },
completed: { colorScheme: 'green', translationKey: 'queue.completed' },
failed: { colorScheme: 'red', translationKey: 'queue.failed' },
canceled: { colorScheme: 'orange', translationKey: 'queue.canceled' },
};
const QueueStatusDot = ({ status }: { status: SessionQueueItemStatus }) => {
const sx = useMemo(
() => ({
w: 2,
h: 2,
bg: `${STATUSES[status].colorScheme}.${500}`,
_dark: {
bg: `${STATUSES[status].colorScheme}.${400}`,
},
borderRadius: '100%',
}),
[status]
);
return <Box sx={sx} />;
};
export default memo(QueueStatusDot);

View File

@ -1,4 +0,0 @@
export const formatNumberShort = (num: number) =>
Intl.NumberFormat('en-US', {
notation: 'standard',
}).format(num);

View File

@ -1,82 +0,0 @@
import {
Flex,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { LOCALSTORAGE_KEYS, LOCALSTORAGE_PREFIX } from 'app/store/constants';
import IAIButton from 'common/components/IAIButton';
import { memo, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
onSettingsModalClose: () => void;
};
const ResetWebUIButton = ({ onSettingsModalClose }: Props) => {
const { t } = useTranslation();
const [countdown, setCountdown] = useState(5);
const {
isOpen: isRefreshModalOpen,
onOpen: onRefreshModalOpen,
onClose: onRefreshModalClose,
} = useDisclosure();
const handleClickResetWebUI = useCallback(() => {
// Only remove our keys
Object.keys(window.localStorage).forEach((key) => {
if (
LOCALSTORAGE_KEYS.includes(key) ||
key.startsWith(LOCALSTORAGE_PREFIX)
) {
localStorage.removeItem(key);
}
});
onSettingsModalClose();
onRefreshModalOpen();
setInterval(() => setCountdown((prev) => prev - 1), 1000);
}, [onSettingsModalClose, onRefreshModalOpen]);
useEffect(() => {
if (countdown <= 0) {
window.location.reload();
}
}, [countdown]);
return (
<>
<IAIButton colorScheme="error" onClick={handleClickResetWebUI}>
{t('settings.resetWebUI')}
</IAIButton>
<Modal
closeOnOverlayClick={false}
isOpen={isRefreshModalOpen}
onClose={onRefreshModalClose}
isCentered
closeOnEsc={false}
>
<ModalOverlay backdropFilter="blur(40px)" />
<ModalContent>
<ModalHeader />
<ModalBody>
<Flex justifyContent="center">
<Text fontSize="lg">
<Text>{t('settings.resetComplete')}</Text>
<Text>Reloading in {countdown}...</Text>
</Text>
</Flex>
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
</>
);
};
export default memo(ResetWebUIButton);

View File

@ -1,3 +0,0 @@
import { createAction } from '@reduxjs/toolkit';
export const sessionReadyToInvoke = createAction('system/sessionReadyToInvoke');

View File

@ -1,62 +0,0 @@
// TODO: split system slice inot this
// import type { PayloadAction } from '@reduxjs/toolkit';
// import { createSlice } from '@reduxjs/toolkit';
// import { socketSubscribed, socketUnsubscribed } from 'services/events/actions';
// export type SessionState = {
// /**
// * The current socket session id
// */
// sessionId: string;
// /**
// * Whether the current session is a canvas session. Needed to manage the staging area.
// */
// isCanvasSession: boolean;
// /**
// * When a session is canceled, its ID is stored here until a new session is created.
// */
// canceledSessionId: string;
// };
// export const initialSessionState: SessionState = {
// sessionId: '',
// isCanvasSession: false,
// canceledSessionId: '',
// };
// export const sessionSlice = createSlice({
// name: 'session',
// initialState: initialSessionState,
// reducers: {
// sessionIdChanged: (state, action: PayloadAction<string>) => {
// state.sessionId = action.payload;
// },
// isCanvasSessionChanged: (state, action: PayloadAction<boolean>) => {
// state.isCanvasSession = action.payload;
// },
// },
// extraReducers: (builder) => {
// /**
// * Socket Subscribed
// */
// builder.addCase(socketSubscribed, (state, action) => {
// state.sessionId = action.payload.sessionId;
// state.canceledSessionId = '';
// });
// /**
// * Socket Unsubscribed
// */
// builder.addCase(socketUnsubscribed, (state) => {
// state.sessionId = '';
// });
// },
// });
// export const { sessionIdChanged, isCanvasSessionChanged } =
// sessionSlice.actions;
// export default sessionSlice.reducer;
export default {};

View File

@ -1,22 +0,0 @@
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { PropsWithChildren, memo } from 'react';
const OverlayScrollable = (props: PropsWithChildren) => {
return (
<OverlayScrollbarsComponent
defer
style={{ height: '100%', width: '100%' }}
options={{
scrollbars: {
visibility: 'auto',
autoHide: 'move',
autoHideDelay: 1300,
theme: 'os-theme-dark',
},
overflow: { x: 'hidden' },
}}
>
{props.children}
</OverlayScrollbarsComponent>
);
};
export default memo(OverlayScrollable);

View File

@ -1,3 +0,0 @@
import { RootState } from 'app/store/store';
export const modelmanagerSelector = (state: RootState) => state.modelmanager;

View File

@ -1,12 +0,0 @@
import { Flex } from '@chakra-ui/react';
import UnifiedCanvasBrushSettings from './UnifiedCanvasBrushSettings';
import UnifiedCanvasLimitStrokesToBox from './UnifiedCanvasLimitStrokesToBox';
export default function UnifiedCanvasBaseBrushSettings() {
return (
<Flex gap={4} alignItems="center">
<UnifiedCanvasBrushSettings />
<UnifiedCanvasLimitStrokesToBox />
</Flex>
);
}

View File

@ -1,12 +0,0 @@
import { Flex } from '@chakra-ui/react';
import UnifiedCanvasBrushSize from './UnifiedCanvasBrushSize';
import UnifiedCanvasColorPicker from './UnifiedCanvasColorPicker';
export default function UnifiedCanvasBrushSettings() {
return (
<Flex columnGap={4} alignItems="center">
<UnifiedCanvasBrushSize />
<UnifiedCanvasColorPicker />
</Flex>
);
}

View File

@ -1,54 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setBrushSize } from 'features/canvas/store/canvasSlice';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasBrushSize() {
const dispatch = useAppDispatch();
const brushSize = useAppSelector(
(state: RootState) => state.canvas.brushSize
);
const { t } = useTranslation();
const isStaging = useAppSelector(isStagingSelector);
useHotkeys(
['BracketLeft'],
() => {
dispatch(setBrushSize(Math.max(brushSize - 5, 5)));
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushSize]
);
useHotkeys(
['BracketRight'],
() => {
dispatch(setBrushSize(Math.min(brushSize + 5, 500)));
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushSize]
);
return (
<IAISlider
label={t('unifiedCanvas.brushSize')}
value={brushSize}
withInput
onChange={(newSize) => dispatch(setBrushSize(newSize))}
sliderNumberInputProps={{ max: 500 }}
isCompact
/>
);
}

View File

@ -1,25 +0,0 @@
import { useAppDispatch } from 'app/store/storeHooks';
import IAIButton from 'common/components/IAIButton';
import { clearMask } from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
export default function UnifiedCanvasClearMask() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleClearMask = () => dispatch(clearMask());
return (
<IAIButton
size="sm"
leftIcon={<FaTrash />}
onClick={handleClearMask}
tooltip={`${t('unifiedCanvas.clearMask')} (Shift+C)`}
>
{t('unifiedCanvas.betaClear')}
</IAIButton>
);
}

View File

@ -1,128 +0,0 @@
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIColorPicker from 'common/components/IAIColorPicker';
import IAIPopover from 'common/components/IAIPopover';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { setBrushColor, setMaskColor } from 'features/canvas/store/canvasSlice';
import { clamp, isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook';
const selector = createSelector(
[canvasSelector, isStagingSelector],
(canvas, isStaging) => {
const { brushColor, maskColor, layer } = canvas;
return {
brushColor,
maskColor,
layer,
isStaging,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
export default function UnifiedCanvasColorPicker() {
const dispatch = useAppDispatch();
const { brushColor, maskColor, layer, isStaging } = useAppSelector(selector);
const currentColorDisplay = () => {
if (layer === 'base') {
return `rgba(${brushColor.r},${brushColor.g},${brushColor.b},${brushColor.a})`;
}
if (layer === 'mask') {
return `rgba(${maskColor.r},${maskColor.g},${maskColor.b},${maskColor.a})`;
}
};
useHotkeys(
['shift+BracketLeft'],
() => {
dispatch(
setBrushColor({
...brushColor,
a: clamp(brushColor.a - 0.05, 0.05, 1),
})
);
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushColor]
);
useHotkeys(
['shift+BracketRight'],
() => {
dispatch(
setBrushColor({
...brushColor,
a: clamp(brushColor.a + 0.05, 0.05, 1),
})
);
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[brushColor]
);
return (
<IAIPopover
triggerComponent={
<Box
sx={{
width: 7,
height: 7,
minWidth: 7,
minHeight: 7,
borderRadius: 'full',
bg: currentColorDisplay(),
cursor: 'pointer',
}}
/>
}
>
<Flex minWidth={60} direction="column" gap={4} width="100%">
{layer === 'base' && (
<Box
sx={{
width: '100%',
paddingTop: 2,
paddingBottom: 2,
}}
>
<IAIColorPicker
color={brushColor}
onChange={(newColor) => dispatch(setBrushColor(newColor))}
/>
</Box>
)}
{layer === 'mask' && (
<Box
sx={{
width: '100%',
paddingTop: 2,
paddingBottom: 2,
}}
>
<IAIColorPicker
color={maskColor}
onChange={(newColor) => dispatch(setMaskColor(newColor))}
/>
</Box>
)}
</Flex>
</IAIPopover>
);
}

View File

@ -1,25 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasDarkenOutsideSelection() {
const shouldDarkenOutsideBoundingBox = useAppSelector(
(state: RootState) => state.canvas.shouldDarkenOutsideBoundingBox
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
return (
<IAISimpleCheckbox
label={t('unifiedCanvas.betaDarkenOutside')}
isChecked={shouldDarkenOutsideBoundingBox}
onChange={(e) =>
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))
}
/>
);
}

View File

@ -1,25 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasEnableMask() {
const isMaskEnabled = useAppSelector(
(state: RootState) => state.canvas.isMaskEnabled
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleToggleEnableMask = () =>
dispatch(setIsMaskEnabled(!isMaskEnabled));
return (
<IAISimpleCheckbox
label={`${t('unifiedCanvas.enableMask')} (H)`}
isChecked={isMaskEnabled}
onChange={handleToggleEnableMask}
/>
);
}

View File

@ -1,25 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import { setShouldRestrictStrokesToBox } from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasLimitStrokesToBox() {
const dispatch = useAppDispatch();
const shouldRestrictStrokesToBox = useAppSelector(
(state: RootState) => state.canvas.shouldRestrictStrokesToBox
);
const { t } = useTranslation();
return (
<IAISimpleCheckbox
label={t('unifiedCanvas.betaLimitToBox')}
isChecked={shouldRestrictStrokesToBox}
onChange={(e) =>
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
}
/>
);
}

View File

@ -1,16 +0,0 @@
import { Flex } from '@chakra-ui/react';
import UnifiedCanvasBrushSettings from './UnifiedCanvasBrushSettings';
import UnifiedCanvasClearMask from './UnifiedCanvasClearMask';
import UnifiedCanvasEnableMask from './UnifiedCanvasEnableMask';
import UnifiedCanvasPreserveMask from './UnifiedCanvasPreserveMask';
export default function UnifiedCanvasMaskBrushSettings() {
return (
<Flex gap={4} alignItems="center">
<UnifiedCanvasBrushSettings />
<UnifiedCanvasEnableMask />
<UnifiedCanvasPreserveMask />
<UnifiedCanvasClearMask />
</Flex>
);
}

View File

@ -1,14 +0,0 @@
import { Flex } from '@chakra-ui/layout';
import UnifiedCanvasDarkenOutsideSelection from './UnifiedCanvasDarkenOutsideSelection';
import UnifiedCanvasShowGrid from './UnifiedCanvasShowGrid';
import UnifiedCanvasSnapToGrid from './UnifiedCanvasSnapToGrid';
export default function UnifiedCanvasMoveSettings() {
return (
<Flex alignItems="center" gap={4}>
<UnifiedCanvasShowGrid />
<UnifiedCanvasSnapToGrid />
<UnifiedCanvasDarkenOutsideSelection />
</Flex>
);
}

View File

@ -1,22 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasPreserveMask() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const shouldPreserveMaskedArea = useAppSelector(
(state: RootState) => state.canvas.shouldPreserveMaskedArea
);
return (
<IAISimpleCheckbox
label={t('unifiedCanvas.betaPreserveMasked')}
isChecked={shouldPreserveMaskedArea}
onChange={(e) => dispatch(setShouldPreserveMaskedArea(e.target.checked))}
/>
);
}

View File

@ -1,113 +0,0 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import {
setShouldAntialias,
setShouldAutoSave,
setShouldCropToBoundingBoxOnSave,
setShouldShowCanvasDebugInfo,
setShouldShowIntermediates,
} from 'features/canvas/store/canvasSlice';
import { FaWrench } from 'react-icons/fa';
import ClearCanvasHistoryButtonModal from 'features/canvas/components/ClearCanvasHistoryButtonModal';
import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next';
import { memo } from 'react';
export const canvasControlsSelector = createSelector(
[canvasSelector],
(canvas) => {
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
shouldAntialias,
} = canvas;
return {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
shouldAntialias,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
const UnifiedCanvasSettings = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
shouldAutoSave,
shouldCropToBoundingBoxOnSave,
shouldShowCanvasDebugInfo,
shouldShowIntermediates,
shouldAntialias,
} = useAppSelector(canvasControlsSelector);
return (
<IAIPopover
isLazy={false}
triggerComponent={
<IAIIconButton
tooltip={t('unifiedCanvas.canvasSettings')}
tooltipProps={{
placement: 'bottom',
}}
aria-label={t('unifiedCanvas.canvasSettings')}
icon={<FaWrench />}
/>
}
>
<Flex direction="column" gap={2}>
<IAISimpleCheckbox
label={t('unifiedCanvas.showIntermediates')}
isChecked={shouldShowIntermediates}
onChange={(e) =>
dispatch(setShouldShowIntermediates(e.target.checked))
}
/>
<IAISimpleCheckbox
label={t('unifiedCanvas.autoSaveToGallery')}
isChecked={shouldAutoSave}
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
/>
<IAISimpleCheckbox
label={t('unifiedCanvas.saveBoxRegionOnly')}
isChecked={shouldCropToBoundingBoxOnSave}
onChange={(e) =>
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
}
/>
<IAISimpleCheckbox
label={t('unifiedCanvas.showCanvasDebugInfo')}
isChecked={shouldShowCanvasDebugInfo}
onChange={(e) =>
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))
}
/>
<IAISimpleCheckbox
label={t('unifiedCanvas.antialiasing')}
isChecked={shouldAntialias}
onChange={(e) => dispatch(setShouldAntialias(e.target.checked))}
/>
<ClearCanvasHistoryButtonModal />
</Flex>
</IAIPopover>
);
};
export default memo(UnifiedCanvasSettings);

View File

@ -1,22 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import { setShouldShowGrid } from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasShowGrid() {
const shouldShowGrid = useAppSelector(
(state: RootState) => state.canvas.shouldShowGrid
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
return (
<IAISimpleCheckbox
label={t('unifiedCanvas.showGrid')}
isChecked={shouldShowGrid}
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
/>
);
}

View File

@ -1,26 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice';
import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export default function UnifiedCanvasSnapToGrid() {
const shouldSnapToGrid = useAppSelector(
(state: RootState) => state.canvas.shouldSnapToGrid
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChangeShouldSnapToGrid = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldSnapToGrid(e.target.checked));
return (
<IAISimpleCheckbox
label={`${t('unifiedCanvas.snapToGrid')} (N)`}
isChecked={shouldSnapToGrid}
onChange={handleChangeShouldSnapToGrid}
/>
);
}

View File

@ -1,41 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { Flex } from '@chakra-ui/react';
import { isEqual } from 'lodash-es';
import UnifiedCanvasBaseBrushSettings from './UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings';
import UnifiedCanvasMaskBrushSettings from './UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings';
import UnifiedCanvasMoveSettings from './UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings';
const selector = createSelector(
[canvasSelector],
(canvas) => {
const { tool, layer } = canvas;
return {
tool,
layer,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
export default function UnifiedCanvasToolSettingsBeta() {
const { tool, layer } = useAppSelector(selector);
return (
<Flex height={8} minHeight={8} maxHeight={8} alignItems="center">
{layer == 'base' && ['brush', 'eraser', 'colorPicker'].includes(tool) && (
<UnifiedCanvasBaseBrushSettings />
)}
{layer == 'mask' && ['brush', 'eraser', 'colorPicker'].includes(tool) && (
<UnifiedCanvasMaskBrushSettings />
)}
{tool == 'move' && <UnifiedCanvasMoveSettings />}
</Flex>
);
}

View File

@ -1,50 +0,0 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
import { useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaCopy } from 'react-icons/fa';
export default function UnifiedCanvasCopyToClipboard() {
const isStaging = useAppSelector(isStagingSelector);
const { isClipboardAPIAvailable } = useCopyImageToClipboard();
const dispatch = useAppDispatch();
const { t } = useTranslation();
useHotkeys(
['meta+c', 'ctrl+c'],
() => {
handleCopyImageToClipboard();
},
{
enabled: () => !isStaging && isClipboardAPIAvailable,
preventDefault: true,
},
[isClipboardAPIAvailable]
);
const handleCopyImageToClipboard = useCallback(() => {
if (!isClipboardAPIAvailable) {
return;
}
dispatch(canvasCopiedToClipboard());
}, [dispatch, isClipboardAPIAvailable]);
if (!isClipboardAPIAvailable) {
return null;
}
return (
<IAIIconButton
aria-label={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
tooltip={`${t('unifiedCanvas.copyToClipboard')} (Cmd/Ctrl+C)`}
icon={<FaCopy />}
onClick={handleCopyImageToClipboard}
isDisabled={isStaging}
/>
);
}

View File

@ -1,43 +0,0 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaDownload } from 'react-icons/fa';
export default function UnifiedCanvasDownloadImage() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const canvasBaseLayer = getCanvasBaseLayer();
const isStaging = useAppSelector(isStagingSelector);
useHotkeys(
['shift+d'],
() => {
handleDownloadAsImage();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[canvasBaseLayer]
);
const handleDownloadAsImage = () => {
dispatch(canvasDownloadedAsImage());
};
return (
<IAIIconButton
aria-label={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
tooltip={`${t('unifiedCanvas.downloadAsImage')} (Shift+D)`}
icon={<FaDownload />}
onClick={handleDownloadAsImage}
isDisabled={isStaging}
/>
);
}

View File

@ -1,28 +0,0 @@
import { useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { useTranslation } from 'react-i18next';
import { FaUpload } from 'react-icons/fa';
export default function UnifiedCanvasFileUploader() {
const isStaging = useAppSelector(isStagingSelector);
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
postUploadAction: { type: 'SET_CANVAS_INITIAL_IMAGE' },
});
const { t } = useTranslation();
return (
<>
<IAIIconButton
aria-label={t('common.upload')}
tooltip={t('common.upload')}
icon={<FaUpload />}
isDisabled={isStaging}
{...getUploadButtonProps()}
/>
<input {...getUploadInputProps()} />
</>
);
}

View File

@ -1,71 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import {
canvasSelector,
isStagingSelector,
} from 'features/canvas/store/canvasSelectors';
import { setIsMaskEnabled, setLayer } from 'features/canvas/store/canvasSlice';
import {
CanvasLayer,
LAYER_NAMES_DICT,
} from 'features/canvas/store/canvasTypes';
import { isEqual } from 'lodash-es';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[canvasSelector, isStagingSelector],
(canvas, isStaging) => {
const { layer, isMaskEnabled } = canvas;
return { layer, isMaskEnabled, isStaging };
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
export default function UnifiedCanvasLayerSelect() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { layer, isMaskEnabled, isStaging } = useAppSelector(selector);
const handleToggleMaskLayer = () => {
dispatch(setLayer(layer === 'mask' ? 'base' : 'mask'));
};
useHotkeys(
['q'],
() => {
handleToggleMaskLayer();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[layer]
);
const handleChangeLayer = (v: string) => {
const newLayer = v as CanvasLayer;
dispatch(setLayer(newLayer));
if (newLayer === 'mask' && !isMaskEnabled) {
dispatch(setIsMaskEnabled(true));
}
};
return (
<IAIMantineSelect
tooltip={`${t('unifiedCanvas.layer')} (Q)`}
aria-label={`${t('unifiedCanvas.layer')} (Q)`}
value={layer}
data={LAYER_NAMES_DICT}
onChange={handleChangeLayer}
disabled={isStaging}
w="full"
/>
);
}

View File

@ -1,37 +0,0 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasMerged } from 'features/canvas/store/actions';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaLayerGroup } from 'react-icons/fa';
export default function UnifiedCanvasMergeVisible() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const isStaging = useAppSelector(isStagingSelector);
useHotkeys(
['shift+m'],
() => {
handleMergeVisible();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
const handleMergeVisible = () => {
dispatch(canvasMerged());
};
return (
<IAIIconButton
aria-label={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
tooltip={`${t('unifiedCanvas.mergeVisible')} (Shift+M)`}
icon={<FaLayerGroup />}
onClick={handleMergeVisible}
isDisabled={isStaging}
/>
);
}

View File

@ -1,39 +0,0 @@
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { setTool } from 'features/canvas/store/canvasSlice';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaArrowsAlt } from 'react-icons/fa';
export default function UnifiedCanvasMoveTool() {
const tool = useAppSelector((state: RootState) => state.canvas.tool);
const isStaging = useAppSelector(isStagingSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
useHotkeys(
['v'],
() => {
handleSelectMoveTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
const handleSelectMoveTool = () => dispatch(setTool('move'));
return (
<IAIIconButton
aria-label={`${t('unifiedCanvas.move')} (V)`}
tooltip={`${t('unifiedCanvas.move')} (V)`}
icon={<FaArrowsAlt />}
isChecked={tool === 'move' || isStaging}
onClick={handleSelectMoveTool}
/>
);
}

View File

@ -1,26 +0,0 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { resetCanvas } from 'features/canvas/store/canvasSlice';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
export default function UnifiedCanvasResetCanvas() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const isStaging = useAppSelector(isStagingSelector);
const handleResetCanvas = () => {
dispatch(resetCanvas());
};
return (
<IAIIconButton
aria-label={t('unifiedCanvas.clearCanvas')}
tooltip={t('unifiedCanvas.clearCanvas')}
icon={<FaTrash />}
onClick={handleResetCanvas}
isDisabled={isStaging}
colorScheme="error"
/>
);
}

View File

@ -1,55 +0,0 @@
import { useAppDispatch } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { useSingleAndDoubleClick } from 'common/hooks/useSingleAndDoubleClick';
import { resetCanvasView } from 'features/canvas/store/canvasSlice';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaCrosshairs } from 'react-icons/fa';
export default function UnifiedCanvasResetView() {
const canvasBaseLayer = getCanvasBaseLayer();
const dispatch = useAppDispatch();
const { t } = useTranslation();
useHotkeys(
['r'],
() => {
handleResetCanvasView();
},
{
enabled: () => true,
preventDefault: true,
},
[canvasBaseLayer]
);
const handleClickResetCanvasView = useSingleAndDoubleClick(
() => handleResetCanvasView(false),
() => handleResetCanvasView(true)
);
const handleResetCanvasView = (shouldScaleTo1 = false) => {
const canvasBaseLayer = getCanvasBaseLayer();
if (!canvasBaseLayer) {
return;
}
const clientRect = canvasBaseLayer.getClientRect({
skipTransform: true,
});
dispatch(
resetCanvasView({
contentRect: clientRect,
shouldScaleTo1,
})
);
};
return (
<IAIIconButton
aria-label={`${t('unifiedCanvas.resetView')} (R)`}
tooltip={`${t('unifiedCanvas.resetView')} (R)`}
icon={<FaCrosshairs />}
onClick={handleClickResetCanvasView}
/>
);
}

View File

@ -1,39 +0,0 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSavedToGallery } from 'features/canvas/store/actions';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaSave } from 'react-icons/fa';
export default function UnifiedCanvasSaveToGallery() {
const isStaging = useAppSelector(isStagingSelector);
const dispatch = useAppDispatch();
const { t } = useTranslation();
useHotkeys(
['shift+s'],
() => {
handleSaveToGallery();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
const handleSaveToGallery = () => {
dispatch(canvasSavedToGallery());
};
return (
<IAIIconButton
aria-label={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
tooltip={`${t('unifiedCanvas.saveToGallery')} (Shift+S)`}
icon={<FaSave />}
onClick={handleSaveToGallery}
isDisabled={isStaging}
/>
);
}

View File

@ -1,172 +0,0 @@
import { ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
addEraseRect,
addFillRect,
setTool,
} from 'features/canvas/store/canvasSlice';
import { isEqual } from 'lodash-es';
import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import {
FaEraser,
FaEyeDropper,
FaFillDrip,
FaPaintBrush,
FaPlus,
} from 'react-icons/fa';
export const selector = createSelector(
[stateSelector, isStagingSelector],
({ canvas }, isStaging) => {
const { tool } = canvas;
return {
tool,
isStaging,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
const UnifiedCanvasToolSelect = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const { tool, isStaging } = useAppSelector(selector);
useHotkeys(
['b'],
() => {
handleSelectBrushTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[]
);
useHotkeys(
['e'],
() => {
handleSelectEraserTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[tool]
);
useHotkeys(
['c'],
() => {
handleSelectColorPickerTool();
},
{
enabled: () => !isStaging,
preventDefault: true,
},
[tool]
);
useHotkeys(
['shift+f'],
() => {
handleFillRect();
},
{
enabled: () => !isStaging,
preventDefault: true,
}
);
useHotkeys(
['delete', 'backspace'],
() => {
handleEraseBoundingBox();
},
{
enabled: () => !isStaging,
preventDefault: true,
}
);
const handleSelectBrushTool = useCallback(
() => dispatch(setTool('brush')),
[dispatch]
);
const handleSelectEraserTool = useCallback(
() => dispatch(setTool('eraser')),
[dispatch]
);
const handleSelectColorPickerTool = useCallback(
() => dispatch(setTool('colorPicker')),
[dispatch]
);
const handleFillRect = useCallback(() => dispatch(addFillRect()), [dispatch]);
const handleEraseBoundingBox = useCallback(
() => dispatch(addEraseRect()),
[dispatch]
);
return (
<Flex flexDirection="column" gap={2}>
<ButtonGroup>
<IAIIconButton
aria-label={`${t('unifiedCanvas.brush')} (B)`}
tooltip={`${t('unifiedCanvas.brush')} (B)`}
icon={<FaPaintBrush />}
isChecked={tool === 'brush' && !isStaging}
onClick={handleSelectBrushTool}
isDisabled={isStaging}
/>
<IAIIconButton
aria-label={`${t('unifiedCanvas.eraser')} (E)`}
tooltip={`${t('unifiedCanvas.eraser')} (B)`}
icon={<FaEraser />}
isChecked={tool === 'eraser' && !isStaging}
isDisabled={isStaging}
onClick={handleSelectEraserTool}
/>
</ButtonGroup>
<ButtonGroup>
<IAIIconButton
aria-label={`${t('unifiedCanvas.fillBoundingBox')} (Shift+F)`}
tooltip={`${t('unifiedCanvas.fillBoundingBox')} (Shift+F)`}
icon={<FaFillDrip />}
isDisabled={isStaging}
onClick={handleFillRect}
/>
<IAIIconButton
aria-label={`${t('unifiedCanvas.eraseBoundingBox')} (Del/Backspace)`}
tooltip={`${t('unifiedCanvas.eraseBoundingBox')} (Del/Backspace)`}
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
isDisabled={isStaging}
onClick={handleEraseBoundingBox}
/>
</ButtonGroup>
<IAIIconButton
aria-label={`${t('unifiedCanvas.colorPicker')} (C)`}
tooltip={`${t('unifiedCanvas.colorPicker')} (C)`}
icon={<FaEyeDropper />}
isChecked={tool === 'colorPicker' && !isStaging}
isDisabled={isStaging}
onClick={handleSelectColorPickerTool}
width="max-content"
/>
</Flex>
);
};
export default memo(UnifiedCanvasToolSelect);

View File

@ -1,53 +0,0 @@
import { Flex } from '@chakra-ui/react';
import IAICanvasRedoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton';
import IAICanvasUndoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasUndoButton';
import { memo } from 'react';
import UnifiedCanvasSettings from './UnifiedCanvasToolSettings/UnifiedCanvasSettings';
import UnifiedCanvasCopyToClipboard from './UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard';
import UnifiedCanvasDownloadImage from './UnifiedCanvasToolbar/UnifiedCanvasDownloadImage';
import UnifiedCanvasFileUploader from './UnifiedCanvasToolbar/UnifiedCanvasFileUploader';
import UnifiedCanvasLayerSelect from './UnifiedCanvasToolbar/UnifiedCanvasLayerSelect';
import UnifiedCanvasMergeVisible from './UnifiedCanvasToolbar/UnifiedCanvasMergeVisible';
import UnifiedCanvasMoveTool from './UnifiedCanvasToolbar/UnifiedCanvasMoveTool';
import UnifiedCanvasResetCanvas from './UnifiedCanvasToolbar/UnifiedCanvasResetCanvas';
import UnifiedCanvasResetView from './UnifiedCanvasToolbar/UnifiedCanvasResetView';
import UnifiedCanvasSaveToGallery from './UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery';
import UnifiedCanvasToolSelect from './UnifiedCanvasToolbar/UnifiedCanvasToolSelect';
const UnifiedCanvasToolbarBeta = () => {
return (
<Flex flexDirection="column" rowGap={2} width="min-content">
<UnifiedCanvasLayerSelect />
<UnifiedCanvasToolSelect />
<Flex gap={2}>
<UnifiedCanvasMoveTool />
<UnifiedCanvasResetView />
</Flex>
<Flex columnGap={2}>
<UnifiedCanvasMergeVisible />
<UnifiedCanvasSaveToGallery />
</Flex>
<Flex columnGap={2}>
<UnifiedCanvasCopyToClipboard />
<UnifiedCanvasDownloadImage />
</Flex>
<Flex gap={2}>
<IAICanvasUndoButton />
<IAICanvasRedoButton />
</Flex>
<Flex gap={2}>
<UnifiedCanvasFileUploader />
<UnifiedCanvasResetCanvas />
</Flex>
<UnifiedCanvasSettings />
</Flex>
);
};
export default memo(UnifiedCanvasToolbarBeta);