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 { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
|
||||
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 { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
@ -16,13 +17,14 @@ type Props = {
|
||||
|
||||
export const ControlLayer = memo(({ id }: Props) => {
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_layer' }), [id]);
|
||||
const editing = useDisclosure({ defaultIsOpen: false });
|
||||
|
||||
return (
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<CanvasEntityHeader>
|
||||
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
||||
<Spacer />
|
||||
<CanvasEntityDeleteButton />
|
||||
</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 { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { CanvasEntityTitleEdit } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
@ -14,13 +15,14 @@ type Props = {
|
||||
|
||||
export const RasterLayer = memo(({ id }: Props) => {
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'raster_layer' }), [id]);
|
||||
const editing = useDisclosure({ defaultIsOpen: false });
|
||||
|
||||
return (
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<CanvasEntityHeader>
|
||||
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
||||
<Spacer />
|
||||
<CanvasEntityDeleteButton />
|
||||
</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 { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
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 { RegionalGuidanceSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
@ -19,12 +20,14 @@ type Props = {
|
||||
|
||||
export const RegionalGuidance = memo(({ id }: Props) => {
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'regional_guidance' }), [id]);
|
||||
const editing = useDisclosure({ defaultIsOpen: false });
|
||||
|
||||
return (
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<CanvasEntityHeader>
|
||||
<CanvasEntityHeader onDoubleClick={editing.onOpen}>
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
{editing.isOpen ? <CanvasEntityTitleEdit onStopEditing={editing.onClose} /> : <CanvasEntityTitle />}
|
||||
<Spacer />
|
||||
<RegionalGuidanceBadges />
|
||||
<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 { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const selectName = useMemo(() => createSelectName(entityIdentifier), [entityIdentifier]);
|
||||
const name = useAppSelector(selectName);
|
||||
const objectCount = useEntityObjectCount(entityIdentifier);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
if (entityIdentifier.type === 'inpaint_mask') {
|
||||
parts.push(t('controlLayers.inpaintMask'));
|
||||
@ -30,7 +50,7 @@ export const useEntityTitle = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}, [entityIdentifier.type, objectCount, t]);
|
||||
}, [entityIdentifier.type, name, objectCount, t]);
|
||||
|
||||
return title;
|
||||
};
|
||||
|
@ -198,6 +198,18 @@ export const canvasV2Slice = createSlice({
|
||||
const { entityIdentifier } = action.payload;
|
||||
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>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
@ -451,6 +463,7 @@ export const {
|
||||
rasterizationCachesInvalidated,
|
||||
// All entities
|
||||
entitySelected,
|
||||
entityNameChanged,
|
||||
entityReset,
|
||||
entityIsEnabledToggled,
|
||||
entityMoved,
|
||||
|
@ -33,6 +33,7 @@ export const controlLayersReducers = {
|
||||
const { id, overrides, isSelected } = action.payload;
|
||||
const layer: CanvasControlLayerState = {
|
||||
id,
|
||||
name: null,
|
||||
type: 'control_layer',
|
||||
isEnabled: true,
|
||||
objects: [],
|
||||
|
@ -30,6 +30,7 @@ export const rasterLayersReducers = {
|
||||
const { id, overrides, isSelected } = action.payload;
|
||||
const layer: CanvasRasterLayerState = {
|
||||
id,
|
||||
name: null,
|
||||
type: 'raster_layer',
|
||||
isEnabled: true,
|
||||
objects: [],
|
||||
|
@ -45,6 +45,7 @@ export const regionsReducers = {
|
||||
const { id } = action.payload;
|
||||
const rg: CanvasRegionalGuidanceState = {
|
||||
id,
|
||||
name: null,
|
||||
type: 'regional_guidance',
|
||||
isEnabled: true,
|
||||
objects: [],
|
||||
|
@ -647,6 +647,7 @@ export type ImageCache = z.infer<typeof zImageCache>;
|
||||
|
||||
export const zCanvasRegionalGuidanceState = z.object({
|
||||
id: zId,
|
||||
name: z.string().nullable(),
|
||||
type: z.literal('regional_guidance'),
|
||||
isEnabled: z.boolean(),
|
||||
position: zCoordinate,
|
||||
@ -730,6 +731,7 @@ export type T2IAdapterConfig = z.infer<typeof zT2IAdapterConfig>;
|
||||
|
||||
export const zCanvasRasterLayerState = z.object({
|
||||
id: zId,
|
||||
name: z.string().nullable(),
|
||||
type: z.literal('raster_layer'),
|
||||
isEnabled: z.boolean(),
|
||||
position: zCoordinate,
|
||||
|
Loading…
Reference in New Issue
Block a user