feat(ui): rename layers

This commit is contained in:
psychedelicious 2024-08-15 11:58:44 +10:00
parent 72919fa34e
commit 5f061ac1e2
10 changed files with 112 additions and 11 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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 />

View File

@ -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';

View File

@ -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;
};

View File

@ -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,

View File

@ -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: [],

View File

@ -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: [],

View File

@ -45,6 +45,7 @@ export const regionsReducers = {
const { id } = action.payload;
const rg: CanvasRegionalGuidanceState = {
id,
name: null,
type: 'regional_guidance',
isEnabled: true,
objects: [],

View File

@ -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,