feat(ui): better editable title

This commit is contained in:
psychedelicious 2024-08-20 12:21:36 +10:00
parent d76509e5cb
commit ab3eb32ec8
7 changed files with 35 additions and 41 deletions

View File

@ -1,12 +1,10 @@
import { Spacer } from '@invoke-ai/ui-library'; import { Spacer } from '@invoke-ai/ui-library';
import { useBoolean } from 'common/hooks/useBoolean';
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 { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
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';
@ -18,14 +16,13 @@ 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 = useBoolean(false);
return ( return (
<EntityIdentifierContext.Provider value={entityIdentifier}> <EntityIdentifierContext.Provider value={entityIdentifier}>
<CanvasEntityContainer> <CanvasEntityContainer>
<CanvasEntityHeader onDoubleClick={editing.setTrue}> <CanvasEntityHeader>
<CanvasEntityEnabledToggle /> <CanvasEntityEnabledToggle />
{editing.isTrue ? <CanvasEntityTitleEdit onStopEditing={editing.setFalse} /> : <CanvasEntityTitle />} <CanvasEntityEditableTitle />
<Spacer /> <Spacer />
<CanvasEntityDeleteButton /> <CanvasEntityDeleteButton />
</CanvasEntityHeader> </CanvasEntityHeader>

View File

@ -1,11 +1,9 @@
import { Spacer } from '@invoke-ai/ui-library'; import { Spacer } from '@invoke-ai/ui-library';
import { useBoolean } from 'common/hooks/useBoolean';
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 { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
import { CanvasEntityTitleEdit } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
import { IPAdapterSettings } from 'features/controlLayers/components/IPAdapter/IPAdapterSettings'; import { IPAdapterSettings } from 'features/controlLayers/components/IPAdapter/IPAdapterSettings';
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';
@ -17,14 +15,13 @@ type Props = {
export const IPAdapter = memo(({ id }: Props) => { export const IPAdapter = memo(({ id }: Props) => {
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'ip_adapter' }), [id]); const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'ip_adapter' }), [id]);
const editing = useBoolean(false);
return ( return (
<EntityIdentifierContext.Provider value={entityIdentifier}> <EntityIdentifierContext.Provider value={entityIdentifier}>
<CanvasEntityContainer> <CanvasEntityContainer>
<CanvasEntityHeader onDoubleClick={editing.setTrue}> <CanvasEntityHeader>
<CanvasEntityEnabledToggle /> <CanvasEntityEnabledToggle />
{editing.isTrue ? <CanvasEntityTitleEdit onStopEditing={editing.setFalse} /> : <CanvasEntityTitle />} <CanvasEntityEditableTitle />
<Spacer /> <Spacer />
<CanvasEntityDeleteButton /> <CanvasEntityDeleteButton />
</CanvasEntityHeader> </CanvasEntityHeader>

View File

@ -1,11 +1,9 @@
import { Spacer } from '@invoke-ai/ui-library'; import { Spacer } from '@invoke-ai/ui-library';
import { useBoolean } from 'common/hooks/useBoolean';
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 { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
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';
@ -16,14 +14,13 @@ 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 = useBoolean(false);
return ( return (
<EntityIdentifierContext.Provider value={entityIdentifier}> <EntityIdentifierContext.Provider value={entityIdentifier}>
<CanvasEntityContainer> <CanvasEntityContainer>
<CanvasEntityHeader onDoubleClick={editing.setTrue}> <CanvasEntityHeader>
<CanvasEntityEnabledToggle /> <CanvasEntityEnabledToggle />
{editing.isTrue ? <CanvasEntityTitleEdit onStopEditing={editing.setFalse} /> : <CanvasEntityTitle />} <CanvasEntityEditableTitle />
<Spacer /> <Spacer />
<CanvasEntityDeleteButton /> <CanvasEntityDeleteButton />
</CanvasEntityHeader> </CanvasEntityHeader>

View File

@ -1,11 +1,9 @@
import { Spacer } from '@invoke-ai/ui-library'; import { Spacer } from '@invoke-ai/ui-library';
import { useBoolean } from 'common/hooks/useBoolean';
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 { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
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';
@ -21,14 +19,13 @@ 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 = useBoolean(false);
return ( return (
<EntityIdentifierContext.Provider value={entityIdentifier}> <EntityIdentifierContext.Provider value={entityIdentifier}>
<CanvasEntityContainer> <CanvasEntityContainer>
<CanvasEntityHeader onDoubleClick={editing.setTrue}> <CanvasEntityHeader>
<CanvasEntityEnabledToggle /> <CanvasEntityEnabledToggle />
{editing.isTrue ? <CanvasEntityTitleEdit onStopEditing={editing.setFalse} /> : <CanvasEntityTitle />} <CanvasEntityEditableTitle />
<Spacer /> <Spacer />
<RegionalGuidanceBadges /> <RegionalGuidanceBadges />
<RegionalGuidanceMaskFillColorPicker /> <RegionalGuidanceMaskFillColorPicker />

View File

@ -53,7 +53,7 @@ export const CanvasEntityHeader = memo(({ children, ...rest }: FlexProps) => {
return ( return (
<ContextMenu renderMenu={renderMenu}> <ContextMenu renderMenu={renderMenu}>
{(ref) => ( {(ref) => (
<Flex ref={ref} gap={2} alignItems="center" p={2} {...rest} cursor="text"> <Flex ref={ref} gap={2} alignItems="center" p={2} {...rest}>
{children} {children}
</Flex> </Flex>
)} )}

View File

@ -1,16 +1,17 @@
import type { TextProps } from '@invoke-ai/ui-library';
import { Text } from '@invoke-ai/ui-library'; import { Text } from '@invoke-ai/ui-library';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityIsSelected } from 'features/controlLayers/hooks/useEntityIsSelected'; import { useEntityIsSelected } from 'features/controlLayers/hooks/useEntityIsSelected';
import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle'; import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle';
import { memo } from 'react'; import { memo } from 'react';
export const CanvasEntityTitle = memo(() => { export const CanvasEntityTitle = memo((props: TextProps) => {
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext();
const isSelected = useEntityIsSelected(entityIdentifier); const isSelected = useEntityIsSelected(entityIdentifier);
const title = useEntityTitle(entityIdentifier); const title = useEntityTitle(entityIdentifier);
return ( return (
<Text size="sm" fontWeight="semibold" userSelect="none" color={isSelected ? 'base.100' : 'base.300'}> <Text size="sm" fontWeight="semibold" userSelect="none" color={isSelected ? 'base.100' : 'base.300'} {...props}>
{title} {title}
</Text> </Text>
); );

View File

@ -1,21 +1,20 @@
import { Input } from '@invoke-ai/ui-library'; import { Input } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useBoolean } from 'common/hooks/useBoolean';
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle'; import { useEntityTitle } from 'features/controlLayers/hooks/useEntityTitle';
import { entityNameChanged } from 'features/controlLayers/store/canvasV2Slice'; import { entityNameChanged } from 'features/controlLayers/store/canvasV2Slice';
import type { ChangeEvent, KeyboardEvent } from 'react'; import type { ChangeEvent, KeyboardEvent } from 'react';
import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useRef, useState } from 'react';
type Props = { export const CanvasEntityEditableTitle = memo(() => {
onStopEditing: () => void;
};
export const CanvasEntityTitleEdit = memo(({ onStopEditing }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const ref = useRef<HTMLInputElement>(null);
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext();
const title = useEntityTitle(entityIdentifier); const title = useEntityTitle(entityIdentifier);
const isEditing = useBoolean(false);
const [localTitle, setLocalTitle] = useState(title); const [localTitle, setLocalTitle] = useState(title);
const ref = useRef<HTMLInputElement>(null);
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setLocalTitle(e.target.value); setLocalTitle(e.target.value);
@ -28,8 +27,8 @@ export const CanvasEntityTitleEdit = memo(({ onStopEditing }: Props) => {
} else if (trimmedTitle !== title) { } else if (trimmedTitle !== title) {
dispatch(entityNameChanged({ entityIdentifier, name: trimmedTitle })); dispatch(entityNameChanged({ entityIdentifier, name: trimmedTitle }));
} }
onStopEditing(); isEditing.setFalse();
}, [dispatch, entityIdentifier, localTitle, onStopEditing, title]); }, [dispatch, entityIdentifier, isEditing, localTitle, title]);
const onKeyDown = useCallback( const onKeyDown = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => { (e: KeyboardEvent<HTMLInputElement>) => {
@ -37,16 +36,22 @@ export const CanvasEntityTitleEdit = memo(({ onStopEditing }: Props) => {
onBlur(); onBlur();
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
setLocalTitle(title); setLocalTitle(title);
onStopEditing(); isEditing.setFalse();
} }
}, },
[onBlur, onStopEditing, title] [isEditing, onBlur, title]
); );
useEffect(() => { useEffect(() => {
ref.current?.focus(); if (isEditing.isTrue) {
ref.current?.select(); ref.current?.focus();
}, []); ref.current?.select();
}
}, [isEditing.isTrue]);
if (!isEditing.isTrue) {
return <CanvasEntityTitle cursor="text" onDoubleClick={isEditing.setTrue} />;
}
return ( return (
<Input <Input
@ -61,4 +66,4 @@ export const CanvasEntityTitleEdit = memo(({ onStopEditing }: Props) => {
); );
}); });
CanvasEntityTitleEdit.displayName = 'CanvasEntityTitleEdit'; CanvasEntityEditableTitle.displayName = 'CanvasEntityTitleEdit';