mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): rename layers
This commit is contained in:
parent
72919fa34e
commit
5f061ac1e2
@ -1,10 +1,11 @@
|
|||||||
import { Spacer } from '@invoke-ai/ui-library';
|
import { Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||||
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
|
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
|
||||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||||
|
import { CanvasEntityTitleEdit } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
|
||||||
import { ControlLayerControlAdapter } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapter';
|
import { ControlLayerControlAdapter } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapter';
|
||||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
@ -16,13 +17,14 @@ type Props = {
|
|||||||
|
|
||||||
export const ControlLayer = memo(({ id }: Props) => {
|
export const ControlLayer = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_layer' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_layer' }), [id]);
|
||||||
|
const editing = useDisclosure({ defaultIsOpen: false });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<CanvasEntityContainer>
|
<CanvasEntityContainer>
|
||||||
<CanvasEntityHeader>
|
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle />
|
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<CanvasEntityDeleteButton />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</CanvasEntityHeader>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Spacer } from '@invoke-ai/ui-library';
|
import { Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||||
|
import { CanvasEntityTitleEdit } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
|
||||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
@ -14,13 +15,14 @@ type Props = {
|
|||||||
|
|
||||||
export const RasterLayer = memo(({ id }: Props) => {
|
export const RasterLayer = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'raster_layer' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'raster_layer' }), [id]);
|
||||||
|
const editing = useDisclosure({ defaultIsOpen: false });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<CanvasEntityContainer>
|
<CanvasEntityContainer>
|
||||||
<CanvasEntityHeader>
|
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle />
|
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<CanvasEntityDeleteButton />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</CanvasEntityHeader>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Spacer } from '@invoke-ai/ui-library';
|
import { Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||||
|
import { CanvasEntityTitleEdit } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
|
||||||
import { RegionalGuidanceBadges } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges';
|
import { RegionalGuidanceBadges } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges';
|
||||||
import { RegionalGuidanceSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings';
|
import { RegionalGuidanceSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings';
|
||||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
@ -19,12 +20,14 @@ type Props = {
|
|||||||
|
|
||||||
export const RegionalGuidance = memo(({ id }: Props) => {
|
export const RegionalGuidance = memo(({ id }: Props) => {
|
||||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'regional_guidance' }), [id]);
|
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'regional_guidance' }), [id]);
|
||||||
|
const editing = useDisclosure({ defaultIsOpen: false });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||||
<CanvasEntityContainer>
|
<CanvasEntityContainer>
|
||||||
<CanvasEntityHeader>
|
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle />
|
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<RegionalGuidanceBadges />
|
<RegionalGuidanceBadges />
|
||||||
<RegionalGuidanceMaskFillColorPicker />
|
<RegionalGuidanceMaskFillColorPicker />
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
import { Input } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle';
|
||||||
|
import { entityNameChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||||
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onStopEditing: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CanvasEntityTitleEdit = memo(({ onStopEditing }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
|
const title = useEntityTitle(entityIdentifier);
|
||||||
|
const [localTitle, setLocalTitle] = useState(title);
|
||||||
|
|
||||||
|
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setLocalTitle(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onBlur = useCallback(() => {
|
||||||
|
const trimmedTitle = localTitle.trim();
|
||||||
|
if (trimmedTitle.length === 0) {
|
||||||
|
dispatch(entityNameChanged({ entityIdentifier, name: null }));
|
||||||
|
} else if (trimmedTitle !== title) {
|
||||||
|
dispatch(entityNameChanged({ entityIdentifier, name: trimmedTitle }));
|
||||||
|
}
|
||||||
|
onStopEditing();
|
||||||
|
}, [dispatch, entityIdentifier, localTitle, onStopEditing, title]);
|
||||||
|
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
onBlur();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
setLocalTitle(title);
|
||||||
|
onStopEditing();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onBlur, onStopEditing, title]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current?.focus();
|
||||||
|
ref.current?.select();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input ref={ref} value={localTitle} onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown} variant="outline" />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CanvasEntityTitleEdit.displayName = 'CanvasEntityTitleEdit';
|
@ -1,15 +1,35 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useEntityObjectCount } from 'features/controlLayers/hooks/useEntityObjectCount';
|
import { useEntityObjectCount } from 'features/controlLayers/hooks/useEntityObjectCount';
|
||||||
|
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
const createSelectName = (entityIdentifier: CanvasEntityIdentifier) =>
|
||||||
|
createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
|
const entity = selectEntity(canvasV2, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (entity.type === 'inpaint_mask') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return entity.name;
|
||||||
|
});
|
||||||
|
|
||||||
export const useEntityTitle = (entityIdentifier: CanvasEntityIdentifier) => {
|
export const useEntityTitle = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const selectName = useMemo(() => createSelectName(entityIdentifier), [entityIdentifier]);
|
||||||
|
const name = useAppSelector(selectName);
|
||||||
const objectCount = useEntityObjectCount(entityIdentifier);
|
const objectCount = useEntityObjectCount(entityIdentifier);
|
||||||
|
|
||||||
const title = useMemo(() => {
|
const title = useMemo(() => {
|
||||||
|
if (name) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (entityIdentifier.type === 'inpaint_mask') {
|
if (entityIdentifier.type === 'inpaint_mask') {
|
||||||
parts.push(t('controlLayers.inpaintMask'));
|
parts.push(t('controlLayers.inpaintMask'));
|
||||||
@ -30,7 +50,7 @@ export const useEntityTitle = (entityIdentifier: CanvasEntityIdentifier) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return parts.join(' ');
|
return parts.join(' ');
|
||||||
}, [entityIdentifier.type, objectCount, t]);
|
}, [entityIdentifier.type, name, objectCount, t]);
|
||||||
|
|
||||||
return title;
|
return title;
|
||||||
};
|
};
|
||||||
|
@ -198,6 +198,18 @@ export const canvasV2Slice = createSlice({
|
|||||||
const { entityIdentifier } = action.payload;
|
const { entityIdentifier } = action.payload;
|
||||||
state.selectedEntityIdentifier = entityIdentifier;
|
state.selectedEntityIdentifier = entityIdentifier;
|
||||||
},
|
},
|
||||||
|
entityNameChanged: (state, action: PayloadAction<EntityIdentifierPayload & { name: string | null }>) => {
|
||||||
|
const { entityIdentifier, name } = action.payload;
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entity.type === 'inpaint_mask') {
|
||||||
|
// Inpaint mask cannot be renamed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entity.name = name;
|
||||||
|
},
|
||||||
entityReset: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
entityReset: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
const { entityIdentifier } = action.payload;
|
const { entityIdentifier } = action.payload;
|
||||||
const entity = selectEntity(state, entityIdentifier);
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
@ -451,6 +463,7 @@ export const {
|
|||||||
rasterizationCachesInvalidated,
|
rasterizationCachesInvalidated,
|
||||||
// All entities
|
// All entities
|
||||||
entitySelected,
|
entitySelected,
|
||||||
|
entityNameChanged,
|
||||||
entityReset,
|
entityReset,
|
||||||
entityIsEnabledToggled,
|
entityIsEnabledToggled,
|
||||||
entityMoved,
|
entityMoved,
|
||||||
|
@ -33,6 +33,7 @@ export const controlLayersReducers = {
|
|||||||
const { id, overrides, isSelected } = action.payload;
|
const { id, overrides, isSelected } = action.payload;
|
||||||
const layer: CanvasControlLayerState = {
|
const layer: CanvasControlLayerState = {
|
||||||
id,
|
id,
|
||||||
|
name: null,
|
||||||
type: 'control_layer',
|
type: 'control_layer',
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
objects: [],
|
objects: [],
|
||||||
|
@ -30,6 +30,7 @@ export const rasterLayersReducers = {
|
|||||||
const { id, overrides, isSelected } = action.payload;
|
const { id, overrides, isSelected } = action.payload;
|
||||||
const layer: CanvasRasterLayerState = {
|
const layer: CanvasRasterLayerState = {
|
||||||
id,
|
id,
|
||||||
|
name: null,
|
||||||
type: 'raster_layer',
|
type: 'raster_layer',
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
objects: [],
|
objects: [],
|
||||||
|
@ -45,6 +45,7 @@ export const regionsReducers = {
|
|||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
const rg: CanvasRegionalGuidanceState = {
|
const rg: CanvasRegionalGuidanceState = {
|
||||||
id,
|
id,
|
||||||
|
name: null,
|
||||||
type: 'regional_guidance',
|
type: 'regional_guidance',
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
objects: [],
|
objects: [],
|
||||||
|
@ -647,6 +647,7 @@ export type ImageCache = z.infer<typeof zImageCache>;
|
|||||||
|
|
||||||
export const zCanvasRegionalGuidanceState = z.object({
|
export const zCanvasRegionalGuidanceState = z.object({
|
||||||
id: zId,
|
id: zId,
|
||||||
|
name: z.string().nullable(),
|
||||||
type: z.literal('regional_guidance'),
|
type: z.literal('regional_guidance'),
|
||||||
isEnabled: z.boolean(),
|
isEnabled: z.boolean(),
|
||||||
position: zCoordinate,
|
position: zCoordinate,
|
||||||
@ -730,6 +731,7 @@ export type T2IAdapterConfig = z.infer<typeof zT2IAdapterConfig>;
|
|||||||
|
|
||||||
export const zCanvasRasterLayerState = z.object({
|
export const zCanvasRasterLayerState = z.object({
|
||||||
id: zId,
|
id: zId,
|
||||||
|
name: z.string().nullable(),
|
||||||
type: z.literal('raster_layer'),
|
type: z.literal('raster_layer'),
|
||||||
isEnabled: z.boolean(),
|
isEnabled: z.boolean(),
|
||||||
position: zCoordinate,
|
position: zCoordinate,
|
||||||
|
Loading…
Reference in New Issue
Block a user