Merge branch 'main' into psyche/fix/ui/depth-anything-select

This commit is contained in:
blessedcoolant 2024-05-13 04:00:07 +05:30 committed by GitHub
commit 52d8efa892
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 201 additions and 128 deletions

View File

@ -261,7 +261,6 @@
"queue": "Queue", "queue": "Queue",
"queueFront": "Add to Front of Queue", "queueFront": "Add to Front of Queue",
"queueBack": "Add to Queue", "queueBack": "Add to Queue",
"queueCountPrediction": "{{promptsCount}} prompts \u00d7 {{iterations}} iterations -> {{count}} generations",
"queueEmpty": "Queue Empty", "queueEmpty": "Queue Empty",
"enqueueing": "Queueing Batch", "enqueueing": "Queueing Batch",
"resume": "Resume", "resume": "Resume",
@ -314,7 +313,13 @@
"batchFailedToQueue": "Failed to Queue Batch", "batchFailedToQueue": "Failed to Queue Batch",
"graphQueued": "Graph queued", "graphQueued": "Graph queued",
"graphFailedToQueue": "Failed to queue graph", "graphFailedToQueue": "Failed to queue graph",
"openQueue": "Open Queue" "openQueue": "Open Queue",
"prompts_one": "Prompt",
"prompts_other": "Prompts",
"iterations_one": "Iteration",
"iterations_other": "Iterations",
"generations_one": "Generation",
"generations_other": "Generations"
}, },
"invocationCache": { "invocationCache": {
"invocationCache": "Invocation Cache", "invocationCache": "Invocation Cache",
@ -934,7 +939,20 @@
"noModelSelected": "No model selected", "noModelSelected": "No model selected",
"noPrompts": "No prompts generated", "noPrompts": "No prompts generated",
"noNodesInGraph": "No nodes in graph", "noNodesInGraph": "No nodes in graph",
"systemDisconnected": "System disconnected" "systemDisconnected": "System disconnected",
"layer": {
"initialImageNoImageSelected": "no initial image selected",
"controlAdapterNoModelSelected": "no Control Adapter model selected",
"controlAdapterIncompatibleBaseModel": "incompatible Control Adapter base model",
"controlAdapterNoImageSelected": "no Control Adapter image selected",
"controlAdapterImageNotProcessed": "Control Adapter image not processed",
"t2iAdapterIncompatibleDimensions": "T2I Adapter requires image dimension to be multiples of 64",
"ipAdapterNoModelSelected": "no IP adapter selected",
"ipAdapterIncompatibleBaseModel": "incompatible IP Adapter base model",
"ipAdapterNoImageSelected": "no IP Adapter image selected",
"rgNoPromptsOrIPAdapters": "no text prompts or IP Adapters",
"rgNoRegion": "no region selected"
}
}, },
"maskBlur": "Mask Blur", "maskBlur": "Mask Blur",
"negativePromptPlaceholder": "Negative Prompt", "negativePromptPlaceholder": "Negative Prompt",
@ -945,8 +963,6 @@
"positivePromptPlaceholder": "Positive Prompt", "positivePromptPlaceholder": "Positive Prompt",
"globalPositivePromptPlaceholder": "Global Positive Prompt", "globalPositivePromptPlaceholder": "Global Positive Prompt",
"iterations": "Iterations", "iterations": "Iterations",
"iterationsWithCount_one": "{{count}} Iteration",
"iterationsWithCount_other": "{{count}} Iterations",
"scale": "Scale", "scale": "Scale",
"scaleBeforeProcessing": "Scale Before Processing", "scaleBeforeProcessing": "Scale Before Processing",
"scaledHeight": "Scaled H", "scaledHeight": "Scaled H",

View File

@ -13,6 +13,7 @@ type UseGroupedModelComboboxArg<T extends AnyModelConfig> = {
onChange: (value: T | null) => void; onChange: (value: T | null) => void;
getIsDisabled?: (model: T) => boolean; getIsDisabled?: (model: T) => boolean;
isLoading?: boolean; isLoading?: boolean;
groupByType?: boolean;
}; };
type UseGroupedModelComboboxReturn = { type UseGroupedModelComboboxReturn = {
@ -23,17 +24,21 @@ type UseGroupedModelComboboxReturn = {
noOptionsMessage: () => string; noOptionsMessage: () => string;
}; };
const groupByBaseFunc = <T extends AnyModelConfig>(model: T) => model.base.toUpperCase();
const groupByBaseAndTypeFunc = <T extends AnyModelConfig>(model: T) =>
`${model.base.toUpperCase()} / ${model.type.replaceAll('_', ' ').toUpperCase()}`;
export const useGroupedModelCombobox = <T extends AnyModelConfig>( export const useGroupedModelCombobox = <T extends AnyModelConfig>(
arg: UseGroupedModelComboboxArg<T> arg: UseGroupedModelComboboxArg<T>
): UseGroupedModelComboboxReturn => { ): UseGroupedModelComboboxReturn => {
const { t } = useTranslation(); const { t } = useTranslation();
const base_model = useAppSelector((s) => s.generation.model?.base ?? 'sdxl'); const base_model = useAppSelector((s) => s.generation.model?.base ?? 'sdxl');
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading } = arg; const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
const options = useMemo<GroupBase<ComboboxOption>[]>(() => { const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
if (!modelConfigs) { if (!modelConfigs) {
return []; return [];
} }
const groupedModels = groupBy(modelConfigs, 'base'); const groupedModels = groupBy(modelConfigs, groupByType ? groupByBaseAndTypeFunc : groupByBaseFunc);
const _options = reduce( const _options = reduce(
groupedModels, groupedModels,
(acc, val, label) => { (acc, val, label) => {
@ -49,9 +54,9 @@ export const useGroupedModelCombobox = <T extends AnyModelConfig>(
}, },
[] as GroupBase<ComboboxOption>[] [] as GroupBase<ComboboxOption>[]
); );
_options.sort((a) => (a.label === base_model ? -1 : 1)); _options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base_model) ? -1 : 1));
return _options; return _options;
}, [getIsDisabled, modelConfigs, base_model]); }, [modelConfigs, groupByType, getIsDisabled, base_model]);
const value = useMemo( const value = useMemo(
() => () =>

View File

@ -6,6 +6,7 @@ import {
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import type { Layer } from 'features/controlLayers/store/types';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
@ -14,9 +15,16 @@ import { selectGenerationSlice } from 'features/parameters/store/generationSlice
import { selectSystemSlice } from 'features/system/store/systemSlice'; import { selectSystemSlice } from 'features/system/store/systemSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import i18n from 'i18next'; import i18n from 'i18next';
import { forEach } from 'lodash-es'; import { forEach, upperFirst } from 'lodash-es';
import { getConnectedEdges } from 'reactflow'; import { getConnectedEdges } from 'reactflow';
const LAYER_TYPE_TO_TKEY: Record<Layer['type'], string> = {
initial_image_layer: 'controlLayers.globalInitialImage',
control_adapter_layer: 'controlLayers.globalControlAdapter',
ip_adapter_layer: 'controlLayers.globalIPAdapter',
regional_guidance_layer: 'controlLayers.regionalGuidance',
};
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[ [
selectControlAdaptersSlice, selectControlAdaptersSlice,
@ -29,21 +37,22 @@ const selector = createMemoizedSelector(
], ],
(controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => { (controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => {
const { model } = generation; const { model } = generation;
const { size } = controlLayers.present;
const { positivePrompt } = controlLayers.present; const { positivePrompt } = controlLayers.present;
const { isConnected } = system; const { isConnected } = system;
const reasons: string[] = []; const reasons: { prefix?: string; content: string }[] = [];
// Cannot generate if not connected // Cannot generate if not connected
if (!isConnected) { if (!isConnected) {
reasons.push(i18n.t('parameters.invoke.systemDisconnected')); reasons.push({ content: i18n.t('parameters.invoke.systemDisconnected') });
} }
if (activeTabName === 'workflows') { if (activeTabName === 'workflows') {
if (nodes.shouldValidateGraph) { if (nodes.shouldValidateGraph) {
if (!nodes.nodes.length) { if (!nodes.nodes.length) {
reasons.push(i18n.t('parameters.invoke.noNodesInGraph')); reasons.push({ content: i18n.t('parameters.invoke.noNodesInGraph') });
} }
nodes.nodes.forEach((node) => { nodes.nodes.forEach((node) => {
@ -55,7 +64,7 @@ const selector = createMemoizedSelector(
if (!nodeTemplate) { if (!nodeTemplate) {
// Node type not found // Node type not found
reasons.push(i18n.t('parameters.invoke.missingNodeTemplate')); reasons.push({ content: i18n.t('parameters.invoke.missingNodeTemplate') });
return; return;
} }
@ -68,17 +77,17 @@ const selector = createMemoizedSelector(
); );
if (!fieldTemplate) { if (!fieldTemplate) {
reasons.push(i18n.t('parameters.invoke.missingFieldTemplate')); reasons.push({ content: i18n.t('parameters.invoke.missingFieldTemplate') });
return; return;
} }
if (fieldTemplate.required && field.value === undefined && !hasConnection) { if (fieldTemplate.required && field.value === undefined && !hasConnection) {
reasons.push( reasons.push({
i18n.t('parameters.invoke.missingInputForField', { content: i18n.t('parameters.invoke.missingInputForField', {
nodeLabel: node.data.label || nodeTemplate.title, nodeLabel: node.data.label || nodeTemplate.title,
fieldLabel: field.label || fieldTemplate.title, fieldLabel: field.label || fieldTemplate.title,
}) }),
); });
return; return;
} }
}); });
@ -86,62 +95,94 @@ const selector = createMemoizedSelector(
} }
} else { } else {
if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) { if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) {
reasons.push(i18n.t('parameters.invoke.noPrompts')); reasons.push({ content: i18n.t('parameters.invoke.noPrompts') });
} }
if (!model) { if (!model) {
reasons.push(i18n.t('parameters.invoke.noModelSelected')); reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
} }
if (activeTabName === 'generation') { if (activeTabName === 'generation') {
// Handling for generation tab // Handling for generation tab
controlLayers.present.layers controlLayers.present.layers
.filter((l) => l.isEnabled) .filter((l) => l.isEnabled)
.flatMap((l) => { .forEach((l, i) => {
const layerLiteral = i18n.t('controlLayers.layers_one');
const layerNumber = i + 1;
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[l.type]);
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
const problems: string[] = [];
if (l.type === 'control_adapter_layer') { if (l.type === 'control_adapter_layer') {
return l.controlAdapter; // Must have model
} else if (l.type === 'ip_adapter_layer') { if (!l.controlAdapter.model) {
return l.ipAdapter; problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
} else if (l.type === 'regional_guidance_layer') { }
return l.ipAdapters; // Model base must match
if (l.controlAdapter.model?.base !== model?.base) {
problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
}
// Must have a control image OR, if it has a processor, it must have a processed image
if (!l.controlAdapter.image) {
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
} else if (l.controlAdapter.processorConfig && !l.controlAdapter.processedImage) {
problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
}
// T2I Adapters require images have dimensions that are multiples of 64
if (l.controlAdapter.type === 't2i_adapter' && (size.width % 64 !== 0 || size.height % 64 !== 0)) {
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions'));
}
} }
return [];
})
.forEach((ca, i) => {
const hasNoModel = !ca.model;
const mismatchedModelBase = ca.model?.base !== model?.base;
const hasNoImage = !ca.image;
const imageNotProcessed =
(ca.type === 'controlnet' || ca.type === 't2i_adapter') && !ca.processedImage && ca.processorConfig;
if (hasNoModel) { if (l.type === 'ip_adapter_layer') {
reasons.push( // Must have model
i18n.t('parameters.invoke.noModelForControlAdapter', { if (!l.ipAdapter.model) {
number: i + 1, problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
}) }
); // Model base must match
if (l.ipAdapter.model?.base !== model?.base) {
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
}
// Must have an image
if (!l.ipAdapter.image) {
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
}
} }
if (mismatchedModelBase) {
// This should never happen, just a sanity check if (l.type === 'initial_image_layer') {
reasons.push( // Must have an image
i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', { if (!l.image) {
number: i + 1, problems.push(i18n.t('parameters.invoke.layer.initialImageNoImageSelected'));
}) }
);
} }
if (hasNoImage) {
reasons.push( if (l.type === 'regional_guidance_layer') {
i18n.t('parameters.invoke.noControlImageForControlAdapter', { // Must have a region
number: i + 1, if (l.maskObjects.length === 0) {
}) problems.push(i18n.t('parameters.invoke.layer.rgNoRegion'));
); }
// Must have at least 1 prompt or IP Adapter
if (l.positivePrompt === null && l.negativePrompt === null && l.ipAdapters.length === 0) {
problems.push(i18n.t('parameters.invoke.layer.rgNoPromptsOrIPAdapters'));
}
l.ipAdapters.forEach((ipAdapter) => {
// Must have model
if (!ipAdapter.model) {
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
}
// Model base must match
if (ipAdapter.model?.base !== model?.base) {
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
}
// Must have an image
if (!ipAdapter.image) {
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
}
});
} }
if (imageNotProcessed) {
reasons.push( if (problems.length) {
i18n.t('parameters.invoke.imageNotProcessedForControlAdapter', { const content = upperFirst(problems.join(', '));
number: i + 1, reasons.push({ prefix, content });
})
);
} }
}); });
} else { } else {
@ -154,29 +195,19 @@ const selector = createMemoizedSelector(
} }
if (!ca.model) { if (!ca.model) {
reasons.push( reasons.push({ content: i18n.t('parameters.invoke.noModelForControlAdapter', { number: i + 1 }) });
i18n.t('parameters.invoke.noModelForControlAdapter', {
number: i + 1,
})
);
} else if (ca.model.base !== model?.base) { } else if (ca.model.base !== model?.base) {
// This should never happen, just a sanity check // This should never happen, just a sanity check
reasons.push( reasons.push({
i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', { content: i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', { number: i + 1 }),
number: i + 1, });
})
);
} }
if ( if (
!ca.controlImage || !ca.controlImage ||
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none') (isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
) { ) {
reasons.push( reasons.push({ content: i18n.t('parameters.invoke.noControlImageForControlAdapter', { number: i + 1 }) });
i18n.t('parameters.invoke.noControlImageForControlAdapter', {
number: i + 1,
})
);
} }
}); });
} }
@ -187,6 +218,6 @@ const selector = createMemoizedSelector(
); );
export const useIsReadyToEnqueue = () => { export const useIsReadyToEnqueue = () => {
const { isReady, reasons } = useAppSelector(selector); const value = useAppSelector(selector);
return { isReady, reasons }; return value;
}; };

View File

@ -42,6 +42,7 @@ export const ControlAdapterModelCombobox = memo(({ modelKey, onChange: onChangeM
selectedModel, selectedModel,
getIsDisabled, getIsDisabled,
isLoading, isLoading,
groupByType: true,
}); });
return ( return (

View File

@ -16,25 +16,26 @@ export const InvokeQueueBackButton = memo(() => {
return ( return (
<Flex pos="relative" flexGrow={1} minW="240px"> <Flex pos="relative" flexGrow={1} minW="240px">
<QueueIterationsNumberInput /> <QueueIterationsNumberInput />
<Button <QueueButtonTooltip>
onClick={queueBack} <Button
isLoading={isLoading || isLoadingDynamicPrompts} onClick={queueBack}
loadingText={invoke} isLoading={isLoading || isLoadingDynamicPrompts}
isDisabled={isDisabled} loadingText={invoke}
rightIcon={<RiSparkling2Fill />} isDisabled={isDisabled}
tooltip={<QueueButtonTooltip />} rightIcon={<RiSparkling2Fill />}
variant="solid" variant="solid"
zIndex={1} zIndex={1}
colorScheme="invokeYellow" colorScheme="invokeYellow"
size="lg" size="lg"
w="calc(100% - 60px)" w="calc(100% - 60px)"
flexShrink={0} flexShrink={0}
justifyContent="space-between" justifyContent="space-between"
spinnerPlacement="end" spinnerPlacement="end"
> >
<span>{invoke}</span> <span>{invoke}</span>
<Spacer /> <Spacer />
</Button> </Button>
</QueueButtonTooltip>
</Flex> </Flex>
); );
}); });

View File

@ -1,10 +1,11 @@
import { Divider, Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-library'; import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue'; import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice'; import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import type { PropsWithChildren } from 'react';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useEnqueueBatchMutation } from 'services/api/endpoints/queue'; import { useEnqueueBatchMutation } from 'services/api/endpoints/queue';
@ -21,17 +22,32 @@ type Props = {
prepend?: boolean; prepend?: boolean;
}; };
export const QueueButtonTooltip = memo(({ prepend = false }: Props) => { export const QueueButtonTooltip = (props: PropsWithChildren<Props>) => {
return (
<Tooltip label={<TooltipContent prepend={props.prepend} />} maxW={512}>
{props.children}
</Tooltip>
);
};
const TooltipContent = memo(({ prepend = false }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isReady, reasons } = useIsReadyToEnqueue(); const { isReady, reasons } = useIsReadyToEnqueue();
const isLoadingDynamicPrompts = useAppSelector((s) => s.dynamicPrompts.isLoading); const isLoadingDynamicPrompts = useAppSelector((s) => s.dynamicPrompts.isLoading);
const promptsCount = useAppSelector(selectPromptsCount); const promptsCount = useAppSelector(selectPromptsCount);
const iterations = useAppSelector((s) => s.generation.iterations); const iterationsCount = useAppSelector((s) => s.generation.iterations);
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
const autoAddBoardName = useBoardName(autoAddBoardId); const autoAddBoardName = useBoardName(autoAddBoardId);
const [_, { isLoading }] = useEnqueueBatchMutation({ const [_, { isLoading }] = useEnqueueBatchMutation({
fixedCacheKey: 'enqueueBatch', fixedCacheKey: 'enqueueBatch',
}); });
const queueCountPredictionLabel = useMemo(() => {
const generationCount = Math.min(promptsCount * iterationsCount, 10000);
const prompts = t('queue.prompts', { count: promptsCount });
const iterations = t('queue.iterations', { count: iterationsCount });
const generations = t('queue.generations', { count: generationCount });
return `${promptsCount} ${prompts} \u00d7 ${iterationsCount} ${iterations} -> ${generationCount} ${generations}`.toLowerCase();
}, [iterationsCount, promptsCount, t]);
const label = useMemo(() => { const label = useMemo(() => {
if (isLoading) { if (isLoading) {
@ -52,20 +68,21 @@ export const QueueButtonTooltip = memo(({ prepend = false }: Props) => {
return ( return (
<Flex flexDir="column" gap={1}> <Flex flexDir="column" gap={1}>
<Text fontWeight="semibold">{label}</Text> <Text fontWeight="semibold">{label}</Text>
<Text> <Text>{queueCountPredictionLabel}</Text>
{t('queue.queueCountPrediction', {
promptsCount,
iterations,
count: Math.min(promptsCount * iterations, 10000),
})}
</Text>
{reasons.length > 0 && ( {reasons.length > 0 && (
<> <>
<Divider opacity={0.2} borderColor="base.900" /> <Divider opacity={0.2} borderColor="base.900" />
<UnorderedList> <UnorderedList>
{reasons.map((reason, i) => ( {reasons.map((reason, i) => (
<ListItem key={`${reason}.${i}`}> <ListItem key={`${reason.content}.${i}`}>
<Text>{reason}</Text> <span>
{reason.prefix && (
<Text as="span" fontWeight="semibold">
{reason.prefix}:{' '}
</Text>
)}
<Text as="span">{reason.content}</Text>
</span>
</ListItem> </ListItem>
))} ))}
</UnorderedList> </UnorderedList>
@ -82,4 +99,4 @@ export const QueueButtonTooltip = memo(({ prepend = false }: Props) => {
); );
}); });
QueueButtonTooltip.displayName = 'QueueButtonTooltip'; TooltipContent.displayName = 'QueueButtonTooltipContent';

View File

@ -10,15 +10,16 @@ const QueueFrontButton = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { queueFront, isLoading, isDisabled } = useQueueFront(); const { queueFront, isLoading, isDisabled } = useQueueFront();
return ( return (
<IconButton <QueueButtonTooltip prepend>
aria-label={t('queue.queueFront')} <IconButton
isDisabled={isDisabled} aria-label={t('queue.queueFront')}
isLoading={isLoading} isDisabled={isDisabled}
onClick={queueFront} isLoading={isLoading}
tooltip={<QueueButtonTooltip prepend />} onClick={queueFront}
icon={<AiFillThunderbolt />} icon={<AiFillThunderbolt />}
size="lg" size="lg"
/> />
</QueueButtonTooltip>
); );
}; };

View File

@ -63,16 +63,17 @@ const FloatingSidePanelButtons = (props: Props) => {
sx={floatingButtonStyles} sx={floatingButtonStyles}
icon={<PiSlidersHorizontalBold size="16px" />} icon={<PiSlidersHorizontalBold size="16px" />}
/> />
<IconButton <QueueButtonTooltip>
aria-label={t('queue.queueBack')} <IconButton
onClick={queueBack} aria-label={t('queue.queueBack')}
isLoading={isLoading} onClick={queueBack}
isDisabled={isDisabled} isLoading={isLoading}
icon={queueButtonIcon} isDisabled={isDisabled}
colorScheme="invokeYellow" icon={queueButtonIcon}
tooltip={<QueueButtonTooltip />} colorScheme="invokeYellow"
sx={floatingButtonStyles} sx={floatingButtonStyles}
/> />
</QueueButtonTooltip>
<CancelCurrentQueueItemIconButton sx={floatingButtonStyles} /> <CancelCurrentQueueItemIconButton sx={floatingButtonStyles} />
</ButtonGroup> </ButtonGroup>
<ClearAllQueueIconButton sx={floatingButtonStyles} onOpen={disclosure.onOpen} /> <ClearAllQueueIconButton sx={floatingButtonStyles} onOpen={disclosure.onOpen} />