mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip drawer component and build
This commit is contained in:
parent
0678803803
commit
777d127c74
@ -1 +0,0 @@
|
||||
.ltr-image-gallery-css-transition-enter{transform:translate(150%)}.ltr-image-gallery-css-transition-enter-active{transform:translate(0);transition:all .12s ease-out}.ltr-image-gallery-css-transition-exit{transform:translate(0)}.ltr-image-gallery-css-transition-exit-active{transform:translate(150%);transition:all .12s ease-out}.rtl-image-gallery-css-transition-enter{transform:translate(-150%)}.rtl-image-gallery-css-transition-enter-active{transform:translate(0);transition:all .12s ease-out}.rtl-image-gallery-css-transition-exit{transform:translate(0)}.rtl-image-gallery-css-transition-exit-active{transform:translate(-150%);transition:all .12s ease-out}.ltr-parameters-panel-transition-enter{transform:translate(-150%)}.ltr-parameters-panel-transition-enter-active{transform:translate(0);transition:all .12s ease-out}.ltr-parameters-panel-transition-exit{transform:translate(0)}.ltr-parameters-panel-transition-exit-active{transform:translate(-150%);transition:all .12s ease-out}.rtl-parameters-panel-transition-enter{transform:translate(150%)}.rtl-parameters-panel-transition-enter-active{transform:translate(0);transition:all .12s ease-out}.rtl-parameters-panel-transition-exit{transform:translate(0)}.rtl-parameters-panel-transition-exit-active{transform:translate(150%);transition:all .12s ease-out}
|
1
invokeai/frontend/web/dist/assets/App-08e5c546.css
vendored
Normal file
1
invokeai/frontend/web/dist/assets/App-08e5c546.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.ltr-image-gallery-css-transition-enter{transform:translate(150%)}.ltr-image-gallery-css-transition-enter-active{transform:translate(0);transition:all .12s ease-out}.ltr-image-gallery-css-transition-exit{transform:translate(0)}.ltr-image-gallery-css-transition-exit-active{transform:translate(150%);transition:all .12s ease-out}.rtl-image-gallery-css-transition-enter{transform:translate(-150%)}.rtl-image-gallery-css-transition-enter-active{transform:translate(0);transition:all .12s ease-out}.rtl-image-gallery-css-transition-exit{transform:translate(0)}.rtl-image-gallery-css-transition-exit-active{transform:translate(-150%);transition:all .12s ease-out}
|
188
invokeai/frontend/web/dist/assets/App-579b8e30.js
vendored
Normal file
188
invokeai/frontend/web/dist/assets/App-579b8e30.js
vendored
Normal file
File diff suppressed because one or more lines are too long
188
invokeai/frontend/web/dist/assets/App-5c94d6ff.js
vendored
188
invokeai/frontend/web/dist/assets/App-5c94d6ff.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
115
invokeai/frontend/web/dist/assets/index-2d974f8a.js
vendored
Normal file
115
invokeai/frontend/web/dist/assets/index-2d974f8a.js
vendored
Normal file
File diff suppressed because one or more lines are too long
115
invokeai/frontend/web/dist/assets/index-c5a5b67c.js
vendored
115
invokeai/frontend/web/dist/assets/index-c5a5b67c.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
invokeai/frontend/web/dist/index.html
vendored
2
invokeai/frontend/web/dist/index.html
vendored
@ -12,7 +12,7 @@
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="./assets/index-c5a5b67c.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-2d974f8a.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index-5483945c.css">
|
||||
</head>
|
||||
|
||||
|
2
invokeai/frontend/web/dist/locales/en.json
vendored
2
invokeai/frontend/web/dist/locales/en.json
vendored
@ -596,7 +596,7 @@
|
||||
"autoSaveToGallery": "Auto Save to Gallery",
|
||||
"saveBoxRegionOnly": "Save Box Region Only",
|
||||
"limitStrokesToBox": "Limit Strokes to Box",
|
||||
"showCanvasDebugInfo": "Show Canvas Debug Info",
|
||||
"showCanvasDebugInfo": "Show Additional Canvas Info",
|
||||
"clearCanvasHistory": "Clear Canvas History",
|
||||
"clearHistory": "Clear History",
|
||||
"clearCanvasHistoryMessage": "Clearing the canvas history leaves your current canvas intact, but irreversibly clears the undo and redo history.",
|
||||
|
40
invokeai/frontend/web/dist/locales/es.json
vendored
40
invokeai/frontend/web/dist/locales/es.json
vendored
@ -63,7 +63,14 @@
|
||||
"back": "Atrás",
|
||||
"statusConvertingModel": "Convertir el modelo",
|
||||
"statusModelConverted": "Modelo adaptado",
|
||||
"statusMergingModels": "Fusionar modelos"
|
||||
"statusMergingModels": "Fusionar modelos",
|
||||
"oceanTheme": "Océano",
|
||||
"langPortuguese": "Portugués",
|
||||
"langKorean": "Coreano",
|
||||
"langHebrew": "Hebreo",
|
||||
"pinOptionsPanel": "Pin del panel de opciones",
|
||||
"loading": "Cargando",
|
||||
"loadingInvokeAI": "Cargando invocar a la IA"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generaciones",
|
||||
@ -385,14 +392,19 @@
|
||||
"modelMergeAlphaHelp": "Alfa controla la fuerza de mezcla de los modelos. Los valores alfa más bajos reducen la influencia del segundo modelo.",
|
||||
"modelMergeInterpAddDifferenceHelp": "En este modo, el Modelo 3 se sustrae primero del Modelo 2. La versión resultante se mezcla con el Modelo 1 con la tasa alfa establecida anteriormente. La versión resultante se mezcla con el Modelo 1 con la tasa alfa establecida anteriormente.",
|
||||
"ignoreMismatch": "Ignorar discrepancias entre modelos seleccionados",
|
||||
"modelMergeHeaderHelp1": "Puede combinar hasta tres modelos diferentes para crear una mezcla que se adapte a sus necesidades.",
|
||||
"modelMergeHeaderHelp1": "Puede unir hasta tres modelos diferentes para crear una combinación que se adapte a sus necesidades.",
|
||||
"inverseSigmoid": "Sigmoideo inverso",
|
||||
"weightedSum": "Modelo de suma ponderada",
|
||||
"sigmoid": "Función sigmoide",
|
||||
"allModels": "Todos los modelos",
|
||||
"repo_id": "Identificador del repositorio",
|
||||
"pathToCustomConfig": "Ruta a la configuración personalizada",
|
||||
"customConfig": "Configuración personalizada"
|
||||
"customConfig": "Configuración personalizada",
|
||||
"v2_base": "v2 (512px)",
|
||||
"none": "ninguno",
|
||||
"pickModelType": "Elige el tipo de modelo",
|
||||
"v2_768": "v2 (768px)",
|
||||
"addDifference": "Añadir una diferencia"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Imágenes",
|
||||
@ -588,5 +600,27 @@
|
||||
"betaDarkenOutside": "Oscurecer fuera",
|
||||
"betaLimitToBox": "Limitar a caja",
|
||||
"betaPreserveMasked": "Preservar área enmascarada"
|
||||
},
|
||||
"accessibility": {
|
||||
"invokeProgressBar": "Activar la barra de progreso",
|
||||
"modelSelect": "Seleccionar modelo",
|
||||
"reset": "Reiniciar",
|
||||
"uploadImage": "Cargar imagen",
|
||||
"previousImage": "Imagen anterior",
|
||||
"nextImage": "Siguiente imagen",
|
||||
"useThisParameter": "Utiliza este parámetro",
|
||||
"copyMetadataJson": "Copiar los metadatos JSON",
|
||||
"exitViewer": "Salir del visor",
|
||||
"zoomIn": "Acercar",
|
||||
"zoomOut": "Alejar",
|
||||
"rotateCounterClockwise": "Girar en sentido antihorario",
|
||||
"rotateClockwise": "Girar en sentido horario",
|
||||
"flipHorizontally": "Voltear horizontalmente",
|
||||
"flipVertically": "Voltear verticalmente",
|
||||
"modifyConfig": "Modificar la configuración",
|
||||
"toggleAutoscroll": "Activar el autodesplazamiento",
|
||||
"toggleLogViewer": "Alternar el visor de registros",
|
||||
"showGallery": "Mostrar galería",
|
||||
"showOptionsPanel": "Mostrar el panel de opciones"
|
||||
}
|
||||
}
|
||||
|
38
invokeai/frontend/web/dist/locales/it.json
vendored
38
invokeai/frontend/web/dist/locales/it.json
vendored
@ -63,7 +63,14 @@
|
||||
"langSimplifiedChinese": "Cinese semplificato",
|
||||
"langDutch": "Olandese",
|
||||
"statusModelConverted": "Modello Convertito",
|
||||
"statusConvertingModel": "Conversione Modello"
|
||||
"statusConvertingModel": "Conversione Modello",
|
||||
"langKorean": "Coreano",
|
||||
"langPortuguese": "Portoghese",
|
||||
"pinOptionsPanel": "Blocca il pannello Opzioni",
|
||||
"loading": "Caricamento in corso",
|
||||
"oceanTheme": "Oceano",
|
||||
"langHebrew": "Ebraico",
|
||||
"loadingInvokeAI": "Caricamento Invoke AI"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generazioni",
|
||||
@ -392,7 +399,12 @@
|
||||
"customSaveLocation": "Ubicazione salvataggio personalizzata",
|
||||
"weightedSum": "Somma pesata",
|
||||
"sigmoid": "Sigmoide",
|
||||
"inverseSigmoid": "Sigmoide inverso"
|
||||
"inverseSigmoid": "Sigmoide inverso",
|
||||
"v2_base": "v2 (512px)",
|
||||
"v2_768": "v2 (768px)",
|
||||
"none": "niente",
|
||||
"addDifference": "Aggiungi differenza",
|
||||
"pickModelType": "Scegli il tipo di modello"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Immagini",
|
||||
@ -588,5 +600,27 @@
|
||||
"betaDarkenOutside": "Oscura all'esterno",
|
||||
"betaLimitToBox": "Limita al rettangolo",
|
||||
"betaPreserveMasked": "Conserva quanto mascherato"
|
||||
},
|
||||
"accessibility": {
|
||||
"modelSelect": "Seleziona modello",
|
||||
"invokeProgressBar": "Barra di avanzamento generazione",
|
||||
"uploadImage": "Carica immagine",
|
||||
"previousImage": "Immagine precedente",
|
||||
"nextImage": "Immagine successiva",
|
||||
"useThisParameter": "Usa questo parametro",
|
||||
"reset": "Reimposta",
|
||||
"copyMetadataJson": "Copia i metadati JSON",
|
||||
"exitViewer": "Esci dal visualizzatore",
|
||||
"zoomIn": "Zoom avanti",
|
||||
"zoomOut": "Zoom Indietro",
|
||||
"rotateCounterClockwise": "Ruotare in senso antiorario",
|
||||
"rotateClockwise": "Ruotare in senso orario",
|
||||
"flipHorizontally": "Capovolgi orizzontalmente",
|
||||
"toggleLogViewer": "Attiva/disattiva visualizzatore registro",
|
||||
"showGallery": "Mostra la galleria immagini",
|
||||
"showOptionsPanel": "Mostra il pannello opzioni",
|
||||
"flipVertically": "Capovolgi verticalmente",
|
||||
"toggleAutoscroll": "Attiva/disattiva lo scorrimento automatico",
|
||||
"modifyConfig": "Modifica configurazione"
|
||||
}
|
||||
}
|
||||
|
556
invokeai/frontend/web/dist/locales/pt.json
vendored
556
invokeai/frontend/web/dist/locales/pt.json
vendored
@ -63,6 +63,560 @@
|
||||
"statusGeneratingOutpainting": "Geração de Ampliação",
|
||||
"statusGenerationComplete": "Geração Completa",
|
||||
"statusMergingModels": "Mesclando Modelos",
|
||||
"statusMergedModels": "Modelos Mesclados"
|
||||
"statusMergedModels": "Modelos Mesclados",
|
||||
"oceanTheme": "Oceano",
|
||||
"pinOptionsPanel": "Fixar painel de opções",
|
||||
"loading": "A carregar",
|
||||
"loadingInvokeAI": "A carregar Invoke AI",
|
||||
"langPortuguese": "Português"
|
||||
},
|
||||
"gallery": {
|
||||
"galleryImageResetSize": "Resetar Imagem",
|
||||
"gallerySettings": "Configurações de Galeria",
|
||||
"maintainAspectRatio": "Mater Proporções",
|
||||
"autoSwitchNewImages": "Trocar para Novas Imagens Automaticamente",
|
||||
"pinGallery": "Fixar Galeria",
|
||||
"singleColumnLayout": "Disposição em Coluna Única",
|
||||
"allImagesLoaded": "Todas as Imagens Carregadas",
|
||||
"loadMore": "Carregar Mais",
|
||||
"noImagesInGallery": "Sem Imagens na Galeria",
|
||||
"generations": "Gerações",
|
||||
"showGenerations": "Mostrar Gerações",
|
||||
"uploads": "Enviados",
|
||||
"showUploads": "Mostrar Enviados",
|
||||
"galleryImageSize": "Tamanho da Imagem"
|
||||
},
|
||||
"hotkeys": {
|
||||
"generalHotkeys": "Atalhos Gerais",
|
||||
"galleryHotkeys": "Atalhos da Galeria",
|
||||
"toggleViewer": {
|
||||
"title": "Ativar Visualizador",
|
||||
"desc": "Abrir e fechar o Visualizador de Imagens"
|
||||
},
|
||||
"maximizeWorkSpace": {
|
||||
"desc": "Fechar painéis e maximixar área de trabalho",
|
||||
"title": "Maximizar a Área de Trabalho"
|
||||
},
|
||||
"changeTabs": {
|
||||
"title": "Mudar Guias",
|
||||
"desc": "Trocar para outra área de trabalho"
|
||||
},
|
||||
"consoleToggle": {
|
||||
"desc": "Abrir e fechar console",
|
||||
"title": "Ativar Console"
|
||||
},
|
||||
"setPrompt": {
|
||||
"title": "Definir Prompt",
|
||||
"desc": "Usar o prompt da imagem atual"
|
||||
},
|
||||
"sendToImageToImage": {
|
||||
"desc": "Manda a imagem atual para Imagem Para Imagem",
|
||||
"title": "Mandar para Imagem Para Imagem"
|
||||
},
|
||||
"previousImage": {
|
||||
"desc": "Mostra a imagem anterior na galeria",
|
||||
"title": "Imagem Anterior"
|
||||
},
|
||||
"nextImage": {
|
||||
"title": "Próxima Imagem",
|
||||
"desc": "Mostra a próxima imagem na galeria"
|
||||
},
|
||||
"decreaseGalleryThumbSize": {
|
||||
"desc": "Diminui o tamanho das thumbs na galeria",
|
||||
"title": "Diminuir Tamanho da Galeria de Imagem"
|
||||
},
|
||||
"selectBrush": {
|
||||
"title": "Selecionar Pincel",
|
||||
"desc": "Seleciona o pincel"
|
||||
},
|
||||
"selectEraser": {
|
||||
"title": "Selecionar Apagador",
|
||||
"desc": "Seleciona o apagador"
|
||||
},
|
||||
"decreaseBrushSize": {
|
||||
"title": "Diminuir Tamanho do Pincel",
|
||||
"desc": "Diminui o tamanho do pincel/apagador"
|
||||
},
|
||||
"increaseBrushOpacity": {
|
||||
"desc": "Aumenta a opacidade do pincel",
|
||||
"title": "Aumentar Opacidade do Pincel"
|
||||
},
|
||||
"moveTool": {
|
||||
"title": "Ferramenta Mover",
|
||||
"desc": "Permite navegar pela tela"
|
||||
},
|
||||
"decreaseBrushOpacity": {
|
||||
"desc": "Diminui a opacidade do pincel",
|
||||
"title": "Diminuir Opacidade do Pincel"
|
||||
},
|
||||
"toggleSnap": {
|
||||
"title": "Ativar Encaixe",
|
||||
"desc": "Ativa Encaixar na Grade"
|
||||
},
|
||||
"quickToggleMove": {
|
||||
"title": "Ativar Mover Rapidamente",
|
||||
"desc": "Temporariamente ativa o modo Mover"
|
||||
},
|
||||
"toggleLayer": {
|
||||
"title": "Ativar Camada",
|
||||
"desc": "Ativa a seleção de camada de máscara/base"
|
||||
},
|
||||
"clearMask": {
|
||||
"title": "Limpar Máscara",
|
||||
"desc": "Limpa toda a máscara"
|
||||
},
|
||||
"hideMask": {
|
||||
"title": "Esconder Máscara",
|
||||
"desc": "Esconde e Revela a máscara"
|
||||
},
|
||||
"mergeVisible": {
|
||||
"title": "Fundir Visível",
|
||||
"desc": "Fundir todas as camadas visíveis das telas"
|
||||
},
|
||||
"downloadImage": {
|
||||
"desc": "Descarregar a tela atual",
|
||||
"title": "Descarregar Imagem"
|
||||
},
|
||||
"undoStroke": {
|
||||
"title": "Desfazer Traço",
|
||||
"desc": "Desfaz um traço de pincel"
|
||||
},
|
||||
"redoStroke": {
|
||||
"title": "Refazer Traço",
|
||||
"desc": "Refaz o traço de pincel"
|
||||
},
|
||||
"keyboardShortcuts": "Atalhos de Teclado",
|
||||
"appHotkeys": "Atalhos do app",
|
||||
"invoke": {
|
||||
"title": "Invocar",
|
||||
"desc": "Gerar uma imagem"
|
||||
},
|
||||
"cancel": {
|
||||
"title": "Cancelar",
|
||||
"desc": "Cancelar geração de imagem"
|
||||
},
|
||||
"focusPrompt": {
|
||||
"title": "Foco do Prompt",
|
||||
"desc": "Foco da área de texto do prompt"
|
||||
},
|
||||
"toggleOptions": {
|
||||
"title": "Ativar Opções",
|
||||
"desc": "Abrir e fechar o painel de opções"
|
||||
},
|
||||
"pinOptions": {
|
||||
"title": "Fixar Opções",
|
||||
"desc": "Fixar o painel de opções"
|
||||
},
|
||||
"closePanels": {
|
||||
"title": "Fechar Painéis",
|
||||
"desc": "Fecha os painéis abertos"
|
||||
},
|
||||
"unifiedCanvasHotkeys": "Atalhos da Tela Unificada",
|
||||
"toggleGallery": {
|
||||
"title": "Ativar Galeria",
|
||||
"desc": "Abrir e fechar a gaveta da galeria"
|
||||
},
|
||||
"setSeed": {
|
||||
"title": "Definir Seed",
|
||||
"desc": "Usar seed da imagem atual"
|
||||
},
|
||||
"setParameters": {
|
||||
"title": "Definir Parâmetros",
|
||||
"desc": "Usar todos os parâmetros da imagem atual"
|
||||
},
|
||||
"restoreFaces": {
|
||||
"title": "Restaurar Rostos",
|
||||
"desc": "Restaurar a imagem atual"
|
||||
},
|
||||
"upscale": {
|
||||
"title": "Redimensionar",
|
||||
"desc": "Redimensionar a imagem atual"
|
||||
},
|
||||
"showInfo": {
|
||||
"title": "Mostrar Informações",
|
||||
"desc": "Mostrar metadados de informações da imagem atual"
|
||||
},
|
||||
"deleteImage": {
|
||||
"title": "Apagar Imagem",
|
||||
"desc": "Apaga a imagem atual"
|
||||
},
|
||||
"toggleGalleryPin": {
|
||||
"title": "Ativar Fixar Galeria",
|
||||
"desc": "Fixa e desafixa a galeria na interface"
|
||||
},
|
||||
"increaseGalleryThumbSize": {
|
||||
"title": "Aumentar Tamanho da Galeria de Imagem",
|
||||
"desc": "Aumenta o tamanho das thumbs na galeria"
|
||||
},
|
||||
"increaseBrushSize": {
|
||||
"title": "Aumentar Tamanho do Pincel",
|
||||
"desc": "Aumenta o tamanho do pincel/apagador"
|
||||
},
|
||||
"fillBoundingBox": {
|
||||
"title": "Preencher Caixa Delimitadora",
|
||||
"desc": "Preenche a caixa delimitadora com a cor do pincel"
|
||||
},
|
||||
"eraseBoundingBox": {
|
||||
"title": "Apagar Caixa Delimitadora",
|
||||
"desc": "Apaga a área da caixa delimitadora"
|
||||
},
|
||||
"colorPicker": {
|
||||
"title": "Selecionar Seletor de Cor",
|
||||
"desc": "Seleciona o seletor de cores"
|
||||
},
|
||||
"showHideBoundingBox": {
|
||||
"title": "Mostrar/Esconder Caixa Delimitadora",
|
||||
"desc": "Ativa a visibilidade da caixa delimitadora"
|
||||
},
|
||||
"saveToGallery": {
|
||||
"title": "Gravara Na Galeria",
|
||||
"desc": "Grava a tela atual na galeria"
|
||||
},
|
||||
"copyToClipboard": {
|
||||
"title": "Copiar para a Área de Transferência",
|
||||
"desc": "Copia a tela atual para a área de transferência"
|
||||
},
|
||||
"resetView": {
|
||||
"title": "Resetar Visualização",
|
||||
"desc": "Reseta Visualização da Tela"
|
||||
},
|
||||
"previousStagingImage": {
|
||||
"title": "Imagem de Preparação Anterior",
|
||||
"desc": "Área de Imagem de Preparação Anterior"
|
||||
},
|
||||
"nextStagingImage": {
|
||||
"title": "Próxima Imagem de Preparação Anterior",
|
||||
"desc": "Próxima Área de Imagem de Preparação Anterior"
|
||||
},
|
||||
"acceptStagingImage": {
|
||||
"title": "Aceitar Imagem de Preparação Anterior",
|
||||
"desc": "Aceitar Área de Imagem de Preparação Anterior"
|
||||
}
|
||||
},
|
||||
"modelManager": {
|
||||
"modelAdded": "Modelo Adicionado",
|
||||
"modelUpdated": "Modelo Atualizado",
|
||||
"modelEntryDeleted": "Entrada de modelo excluída",
|
||||
"description": "Descrição",
|
||||
"modelLocationValidationMsg": "Caminho para onde o seu modelo está localizado.",
|
||||
"repo_id": "Repo ID",
|
||||
"vaeRepoIDValidationMsg": "Repositório Online do seu VAE",
|
||||
"width": "Largura",
|
||||
"widthValidationMsg": "Largura padrão do seu modelo.",
|
||||
"height": "Altura",
|
||||
"heightValidationMsg": "Altura padrão do seu modelo.",
|
||||
"findModels": "Encontrar Modelos",
|
||||
"scanAgain": "Digitalize Novamente",
|
||||
"deselectAll": "Deselecionar Tudo",
|
||||
"showExisting": "Mostrar Existente",
|
||||
"deleteConfig": "Apagar Config",
|
||||
"convertToDiffusersHelpText6": "Deseja converter este modelo?",
|
||||
"mergedModelName": "Nome do modelo mesclado",
|
||||
"alpha": "Alpha",
|
||||
"interpolationType": "Tipo de Interpolação",
|
||||
"modelMergeHeaderHelp1": "Pode mesclar até três modelos diferentes para criar uma mistura que atenda às suas necessidades.",
|
||||
"modelMergeHeaderHelp2": "Apenas Diffusers estão disponíveis para mesclagem. Se deseja mesclar um modelo de checkpoint, por favor, converta-o para Diffusers primeiro.",
|
||||
"modelMergeInterpAddDifferenceHelp": "Neste modo, o Modelo 3 é primeiro subtraído do Modelo 2. A versão resultante é mesclada com o Modelo 1 com a taxa alpha definida acima.",
|
||||
"nameValidationMsg": "Insira um nome para o seu modelo",
|
||||
"descriptionValidationMsg": "Adicione uma descrição para o seu modelo",
|
||||
"config": "Configuração",
|
||||
"modelExists": "Modelo Existe",
|
||||
"selectAndAdd": "Selecione e Adicione Modelos Listados Abaixo",
|
||||
"noModelsFound": "Nenhum Modelo Encontrado",
|
||||
"v2_768": "v2 (768px)",
|
||||
"inpainting": "v1 Inpainting",
|
||||
"customConfig": "Configuração personalizada",
|
||||
"pathToCustomConfig": "Caminho para configuração personalizada",
|
||||
"statusConverting": "A converter",
|
||||
"modelConverted": "Modelo Convertido",
|
||||
"ignoreMismatch": "Ignorar Divergências entre Modelos Selecionados",
|
||||
"addDifference": "Adicionar diferença",
|
||||
"pickModelType": "Escolha o tipo de modelo",
|
||||
"safetensorModels": "SafeTensors",
|
||||
"cannotUseSpaces": "Não pode usar espaços",
|
||||
"addNew": "Adicionar Novo",
|
||||
"addManually": "Adicionar Manualmente",
|
||||
"manual": "Manual",
|
||||
"name": "Nome",
|
||||
"configValidationMsg": "Caminho para o ficheiro de configuração do seu modelo.",
|
||||
"modelLocation": "Localização do modelo",
|
||||
"repoIDValidationMsg": "Repositório Online do seu Modelo",
|
||||
"updateModel": "Atualizar Modelo",
|
||||
"availableModels": "Modelos Disponíveis",
|
||||
"load": "Carregar",
|
||||
"active": "Ativado",
|
||||
"notLoaded": "Não carregado",
|
||||
"deleteModel": "Apagar modelo",
|
||||
"deleteMsg1": "Tem certeza de que deseja apagar esta entrada do modelo de InvokeAI?",
|
||||
"deleteMsg2": "Isso não vai apagar o ficheiro de modelo checkpoint do seu disco. Pode lê-los, se desejar.",
|
||||
"convertToDiffusers": "Converter para Diffusers",
|
||||
"convertToDiffusersHelpText1": "Este modelo será convertido ao formato 🧨 Diffusers.",
|
||||
"convertToDiffusersHelpText2": "Este processo irá substituir a sua entrada de Gestor de Modelos por uma versão Diffusers do mesmo modelo.",
|
||||
"convertToDiffusersHelpText3": "O seu ficheiro de ponto de verificação no disco NÃO será excluído ou modificado de forma alguma. Pode adicionar o seu ponto de verificação ao Gestor de modelos novamente, se desejar.",
|
||||
"convertToDiffusersSaveLocation": "Local para Gravar",
|
||||
"v2_base": "v2 (512px)",
|
||||
"mergeModels": "Mesclar modelos",
|
||||
"modelOne": "Modelo 1",
|
||||
"modelTwo": "Modelo 2",
|
||||
"modelThree": "Modelo 3",
|
||||
"mergedModelSaveLocation": "Local de Salvamento",
|
||||
"merge": "Mesclar",
|
||||
"modelsMerged": "Modelos mesclados",
|
||||
"mergedModelCustomSaveLocation": "Caminho Personalizado",
|
||||
"invokeAIFolder": "Pasta Invoke AI",
|
||||
"inverseSigmoid": "Sigmóide Inversa",
|
||||
"none": "nenhum",
|
||||
"modelManager": "Gerente de Modelo",
|
||||
"model": "Modelo",
|
||||
"allModels": "Todos os Modelos",
|
||||
"checkpointModels": "Checkpoints",
|
||||
"diffusersModels": "Diffusers",
|
||||
"addNewModel": "Adicionar Novo modelo",
|
||||
"addCheckpointModel": "Adicionar Modelo de Checkpoint/Safetensor",
|
||||
"addDiffuserModel": "Adicionar Diffusers",
|
||||
"vaeLocation": "Localização VAE",
|
||||
"vaeLocationValidationMsg": "Caminho para onde o seu VAE está localizado.",
|
||||
"vaeRepoID": "VAE Repo ID",
|
||||
"addModel": "Adicionar Modelo",
|
||||
"search": "Procurar",
|
||||
"cached": "Em cache",
|
||||
"checkpointFolder": "Pasta de Checkpoint",
|
||||
"clearCheckpointFolder": "Apagar Pasta de Checkpoint",
|
||||
"modelsFound": "Modelos Encontrados",
|
||||
"selectFolder": "Selecione a Pasta",
|
||||
"selected": "Selecionada",
|
||||
"selectAll": "Selecionar Tudo",
|
||||
"addSelected": "Adicione Selecionado",
|
||||
"delete": "Apagar",
|
||||
"formMessageDiffusersModelLocation": "Localização dos Modelos Diffusers",
|
||||
"formMessageDiffusersModelLocationDesc": "Por favor entre com ao menos um.",
|
||||
"formMessageDiffusersVAELocation": "Localização do VAE",
|
||||
"formMessageDiffusersVAELocationDesc": "Se não provido, InvokeAI irá procurar pelo ficheiro VAE dentro do local do modelo.",
|
||||
"convert": "Converter",
|
||||
"convertToDiffusersHelpText4": "Este é um processo único. Pode levar cerca de 30 a 60s, a depender das especificações do seu computador.",
|
||||
"convertToDiffusersHelpText5": "Por favor, certifique-se de que tenha espaço suficiente no disco. Os modelos geralmente variam entre 4GB e 7GB de tamanho.",
|
||||
"v1": "v1",
|
||||
"sameFolder": "Mesma pasta",
|
||||
"invokeRoot": "Pasta do InvokeAI",
|
||||
"custom": "Personalizado",
|
||||
"customSaveLocation": "Local de salvamento personalizado",
|
||||
"modelMergeAlphaHelp": "Alpha controla a força da mistura dos modelos. Valores de alpha mais baixos resultam numa influência menor do segundo modelo.",
|
||||
"sigmoid": "Sigmóide",
|
||||
"weightedSum": "Soma Ponderada"
|
||||
},
|
||||
"parameters": {
|
||||
"width": "Largura",
|
||||
"seed": "Seed",
|
||||
"hiresStrength": "Força da Alta Resolução",
|
||||
"negativePrompts": "Indicações negativas",
|
||||
"general": "Geral",
|
||||
"randomizeSeed": "Seed Aleatório",
|
||||
"shuffle": "Embaralhar",
|
||||
"noiseThreshold": "Limite de Ruído",
|
||||
"perlinNoise": "Ruído de Perlin",
|
||||
"variations": "Variatções",
|
||||
"seedWeights": "Pesos da Seed",
|
||||
"restoreFaces": "Restaurar Rostos",
|
||||
"faceRestoration": "Restauração de Rosto",
|
||||
"type": "Tipo",
|
||||
"denoisingStrength": "A força de remoção de ruído",
|
||||
"scale": "Escala",
|
||||
"otherOptions": "Outras Opções",
|
||||
"seamlessTiling": "Ladrilho Sem Fronteira",
|
||||
"hiresOptim": "Otimização de Alta Res",
|
||||
"imageFit": "Caber Imagem Inicial No Tamanho de Saída",
|
||||
"codeformerFidelity": "Fidelidade",
|
||||
"seamSize": "Tamanho da Fronteira",
|
||||
"seamBlur": "Desfoque da Fronteira",
|
||||
"seamStrength": "Força da Fronteira",
|
||||
"seamSteps": "Passos da Fronteira",
|
||||
"tileSize": "Tamanho do Ladrilho",
|
||||
"boundingBoxHeader": "Caixa Delimitadora",
|
||||
"seamCorrectionHeader": "Correção de Fronteira",
|
||||
"infillScalingHeader": "Preencimento e Escala",
|
||||
"img2imgStrength": "Força de Imagem Para Imagem",
|
||||
"toggleLoopback": "Ativar Loopback",
|
||||
"symmetry": "Simetria",
|
||||
"promptPlaceholder": "Digite o prompt aqui. [tokens negativos], (upweight)++, (downweight)--, trocar e misturar estão disponíveis (veja docs)",
|
||||
"sendTo": "Mandar para",
|
||||
"openInViewer": "Abrir No Visualizador",
|
||||
"closeViewer": "Fechar Visualizador",
|
||||
"usePrompt": "Usar Prompt",
|
||||
"deleteImage": "Apagar Imagem",
|
||||
"initialImage": "Imagem inicial",
|
||||
"showOptionsPanel": "Mostrar Painel de Opções",
|
||||
"strength": "Força",
|
||||
"upscaling": "Redimensionando",
|
||||
"upscale": "Redimensionar",
|
||||
"upscaleImage": "Redimensionar Imagem",
|
||||
"scaleBeforeProcessing": "Escala Antes do Processamento",
|
||||
"invoke": "Invocar",
|
||||
"images": "Imagems",
|
||||
"steps": "Passos",
|
||||
"cfgScale": "Escala CFG",
|
||||
"height": "Altura",
|
||||
"sampler": "Amostrador",
|
||||
"imageToImage": "Imagem para Imagem",
|
||||
"variationAmount": "Quntidade de Variatções",
|
||||
"scaledWidth": "L Escalada",
|
||||
"scaledHeight": "A Escalada",
|
||||
"infillMethod": "Método de Preenchimento",
|
||||
"hSymmetryStep": "H Passo de Simetria",
|
||||
"vSymmetryStep": "V Passo de Simetria",
|
||||
"cancel": {
|
||||
"immediate": "Cancelar imediatamente",
|
||||
"schedule": "Cancelar após a iteração atual",
|
||||
"isScheduled": "A cancelar",
|
||||
"setType": "Definir tipo de cancelamento"
|
||||
},
|
||||
"sendToImg2Img": "Mandar para Imagem Para Imagem",
|
||||
"sendToUnifiedCanvas": "Mandar para Tela Unificada",
|
||||
"copyImage": "Copiar imagem",
|
||||
"copyImageToLink": "Copiar Imagem Para a Ligação",
|
||||
"downloadImage": "Descarregar Imagem",
|
||||
"useSeed": "Usar Seed",
|
||||
"useAll": "Usar Todos",
|
||||
"useInitImg": "Usar Imagem Inicial",
|
||||
"info": "Informações"
|
||||
},
|
||||
"settings": {
|
||||
"confirmOnDelete": "Confirmar Antes de Apagar",
|
||||
"displayHelpIcons": "Mostrar Ícones de Ajuda",
|
||||
"useCanvasBeta": "Usar Layout de Telas Beta",
|
||||
"enableImageDebugging": "Ativar Depuração de Imagem",
|
||||
"useSlidersForAll": "Usar deslizadores para todas as opções",
|
||||
"resetWebUIDesc1": "Reiniciar a interface apenas reinicia o cache local do broswer para imagens e configurações lembradas. Não apaga nenhuma imagem do disco.",
|
||||
"models": "Modelos",
|
||||
"displayInProgress": "Mostrar Progresso de Imagens Em Andamento",
|
||||
"saveSteps": "Gravar imagens a cada n passos",
|
||||
"resetWebUI": "Reiniciar Interface",
|
||||
"resetWebUIDesc2": "Se as imagens não estão a aparecer na galeria ou algo mais não está a funcionar, favor tentar reiniciar antes de postar um problema no GitHub.",
|
||||
"resetComplete": "A interface foi reiniciada. Atualize a página para carregar."
|
||||
},
|
||||
"toast": {
|
||||
"uploadFailed": "Envio Falhou",
|
||||
"uploadFailedMultipleImagesDesc": "Várias imagens copiadas, só é permitido uma imagem de cada vez",
|
||||
"uploadFailedUnableToLoadDesc": "Não foj possível carregar o ficheiro",
|
||||
"downloadImageStarted": "Download de Imagem Começou",
|
||||
"imageNotLoadedDesc": "Nenhuma imagem encontrada a enviar para o módulo de imagem para imagem",
|
||||
"imageLinkCopied": "Ligação de Imagem Copiada",
|
||||
"imageNotLoaded": "Nenhuma Imagem Carregada",
|
||||
"parametersFailed": "Problema ao carregar parâmetros",
|
||||
"parametersFailedDesc": "Não foi possível carregar imagem incial.",
|
||||
"seedSet": "Seed Definida",
|
||||
"upscalingFailed": "Redimensionamento Falhou",
|
||||
"promptNotSet": "Prompt Não Definido",
|
||||
"tempFoldersEmptied": "Pasta de Ficheiros Temporários Esvaziada",
|
||||
"imageCopied": "Imagem Copiada",
|
||||
"imageSavedToGallery": "Imagem Salva na Galeria",
|
||||
"canvasMerged": "Tela Fundida",
|
||||
"sentToImageToImage": "Mandar Para Imagem Para Imagem",
|
||||
"sentToUnifiedCanvas": "Enviada para a Tela Unificada",
|
||||
"parametersSet": "Parâmetros Definidos",
|
||||
"parametersNotSet": "Parâmetros Não Definidos",
|
||||
"parametersNotSetDesc": "Nenhum metadado foi encontrado para essa imagem.",
|
||||
"seedNotSet": "Seed Não Definida",
|
||||
"seedNotSetDesc": "Não foi possível achar a seed para a imagem.",
|
||||
"promptSet": "Prompt Definido",
|
||||
"promptNotSetDesc": "Não foi possível achar prompt para essa imagem.",
|
||||
"faceRestoreFailed": "Restauração de Rosto Falhou",
|
||||
"metadataLoadFailed": "Falha ao tentar carregar metadados",
|
||||
"initialImageSet": "Imagem Inicial Definida",
|
||||
"initialImageNotSet": "Imagem Inicial Não Definida",
|
||||
"initialImageNotSetDesc": "Não foi possível carregar imagem incial"
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
"prompt": "Este é o campo de prompt. O prompt inclui objetos de geração e termos estilísticos. Também pode adicionar peso (importância do token) no prompt, mas comandos e parâmetros de CLI não funcionarão.",
|
||||
"other": "Essas opções ativam modos alternativos de processamento para o Invoke. 'Seamless tiling' criará padrões repetidos na saída. 'High resolution' é uma geração em duas etapas com img2img: use essa configuração quando desejar uma imagem maior e mais coerente sem artefatos. Levará mais tempo do que o txt2img usual.",
|
||||
"seed": "O valor da semente afeta o ruído inicial a partir do qual a imagem é formada. Pode usar as sementes já existentes de imagens anteriores. 'Limiar de ruído' é usado para mitigar artefatos em valores CFG altos (experimente a faixa de 0-10) e o Perlin para adicionar ruído Perlin durante a geração: ambos servem para adicionar variação às suas saídas.",
|
||||
"imageToImage": "Image to Image carrega qualquer imagem como inicial, que é então usada para gerar uma nova junto com o prompt. Quanto maior o valor, mais a imagem resultante mudará. Valores de 0.0 a 1.0 são possíveis, a faixa recomendada é de 0.25 a 0.75",
|
||||
"faceCorrection": "Correção de rosto com GFPGAN ou Codeformer: o algoritmo detecta rostos na imagem e corrige quaisquer defeitos. Um valor alto mudará mais a imagem, a resultar em rostos mais atraentes. Codeformer com uma fidelidade maior preserva a imagem original às custas de uma correção de rosto mais forte.",
|
||||
"seamCorrection": "Controla o tratamento das emendas visíveis que ocorrem entre as imagens geradas no canvas.",
|
||||
"gallery": "A galeria exibe as gerações da pasta de saída conforme elas são criadas. As configurações são armazenadas em ficheiros e acessadas pelo menu de contexto.",
|
||||
"variations": "Experimente uma variação com um valor entre 0,1 e 1,0 para mudar o resultado para uma determinada semente. Variações interessantes da semente estão entre 0,1 e 0,3.",
|
||||
"upscale": "Use o ESRGAN para ampliar a imagem imediatamente após a geração.",
|
||||
"boundingBox": "A caixa delimitadora é a mesma que as configurações de largura e altura para Texto para Imagem ou Imagem para Imagem. Apenas a área na caixa será processada.",
|
||||
"infillAndScaling": "Gira os métodos de preenchimento (usados em áreas mascaradas ou apagadas do canvas) e a escala (útil para tamanhos de caixa delimitadora pequenos)."
|
||||
}
|
||||
},
|
||||
"unifiedCanvas": {
|
||||
"emptyTempImagesFolderMessage": "Esvaziar a pasta de ficheiros de imagem temporários também reseta completamente a Tela Unificada. Isso inclui todo o histórico de desfazer/refazer, imagens na área de preparação e a camada base da tela.",
|
||||
"scaledBoundingBox": "Caixa Delimitadora Escalada",
|
||||
"boundingBoxPosition": "Posição da Caixa Delimitadora",
|
||||
"next": "Próximo",
|
||||
"accept": "Aceitar",
|
||||
"showHide": "Mostrar/Esconder",
|
||||
"discardAll": "Descartar Todos",
|
||||
"betaClear": "Limpar",
|
||||
"betaDarkenOutside": "Escurecer Externamente",
|
||||
"base": "Base",
|
||||
"brush": "Pincel",
|
||||
"showIntermediates": "Mostrar Intermediários",
|
||||
"showGrid": "Mostrar Grade",
|
||||
"clearCanvasHistoryConfirm": "Tem certeza que quer limpar o histórico de tela?",
|
||||
"boundingBox": "Caixa Delimitadora",
|
||||
"canvasDimensions": "Dimensões da Tela",
|
||||
"canvasPosition": "Posição da Tela",
|
||||
"cursorPosition": "Posição do cursor",
|
||||
"previous": "Anterior",
|
||||
"betaLimitToBox": "Limitar á Caixa",
|
||||
"layer": "Camada",
|
||||
"mask": "Máscara",
|
||||
"maskingOptions": "Opções de Mascaramento",
|
||||
"enableMask": "Ativar Máscara",
|
||||
"preserveMaskedArea": "Preservar Área da Máscara",
|
||||
"clearMask": "Limpar Máscara",
|
||||
"eraser": "Apagador",
|
||||
"fillBoundingBox": "Preencher Caixa Delimitadora",
|
||||
"eraseBoundingBox": "Apagar Caixa Delimitadora",
|
||||
"colorPicker": "Seletor de Cor",
|
||||
"brushOptions": "Opções de Pincel",
|
||||
"brushSize": "Tamanho",
|
||||
"move": "Mover",
|
||||
"resetView": "Resetar Visualização",
|
||||
"mergeVisible": "Fundir Visível",
|
||||
"saveToGallery": "Gravar na Galeria",
|
||||
"copyToClipboard": "Copiar para a Área de Transferência",
|
||||
"downloadAsImage": "Descarregar Como Imagem",
|
||||
"undo": "Desfazer",
|
||||
"redo": "Refazer",
|
||||
"clearCanvas": "Limpar Tela",
|
||||
"canvasSettings": "Configurações de Tela",
|
||||
"snapToGrid": "Encaixar na Grade",
|
||||
"darkenOutsideSelection": "Escurecer Seleção Externa",
|
||||
"autoSaveToGallery": "Gravar Automaticamente na Galeria",
|
||||
"saveBoxRegionOnly": "Gravar Apenas a Região da Caixa",
|
||||
"limitStrokesToBox": "Limitar Traços à Caixa",
|
||||
"showCanvasDebugInfo": "Mostrar Informações de Depuração daTela",
|
||||
"clearCanvasHistory": "Limpar o Histórico da Tela",
|
||||
"clearHistory": "Limpar Históprico",
|
||||
"clearCanvasHistoryMessage": "Limpar o histórico de tela deixa a sua tela atual intacta, mas limpa de forma irreversível o histórico de desfazer e refazer.",
|
||||
"emptyTempImageFolder": "Esvaziar a Pasta de Ficheiros de Imagem Temporários",
|
||||
"emptyFolder": "Esvaziar Pasta",
|
||||
"emptyTempImagesFolderConfirm": "Tem certeza que quer esvaziar a pasta de ficheiros de imagem temporários?",
|
||||
"activeLayer": "Camada Ativa",
|
||||
"canvasScale": "Escala da Tela",
|
||||
"betaPreserveMasked": "Preservar Máscarado"
|
||||
},
|
||||
"accessibility": {
|
||||
"invokeProgressBar": "Invocar barra de progresso",
|
||||
"reset": "Repôr",
|
||||
"nextImage": "Próxima imagem",
|
||||
"useThisParameter": "Usar este parâmetro",
|
||||
"copyMetadataJson": "Copiar metadados JSON",
|
||||
"zoomIn": "Ampliar",
|
||||
"zoomOut": "Reduzir",
|
||||
"rotateCounterClockwise": "Girar no sentido anti-horário",
|
||||
"rotateClockwise": "Girar no sentido horário",
|
||||
"flipVertically": "Espelhar verticalmente",
|
||||
"modifyConfig": "Modificar config",
|
||||
"toggleAutoscroll": "Alternar rolagem automática",
|
||||
"showGallery": "Mostrar galeria",
|
||||
"showOptionsPanel": "Mostrar painel de opções",
|
||||
"uploadImage": "Enviar imagem",
|
||||
"previousImage": "Imagem anterior",
|
||||
"flipHorizontally": "Espelhar horizontalmente",
|
||||
"toggleLogViewer": "Alternar visualizador de registo"
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,10 @@
|
||||
"statusMergingModels": "Mesclando Modelos",
|
||||
"statusMergedModels": "Modelos Mesclados",
|
||||
"langRussian": "Russo",
|
||||
"langSpanish": "Espanhol"
|
||||
"langSpanish": "Espanhol",
|
||||
"pinOptionsPanel": "Fixar painel de opções",
|
||||
"loadingInvokeAI": "Carregando Invoke AI",
|
||||
"loading": "Carregando"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Gerações",
|
||||
|
38
invokeai/frontend/web/dist/locales/ru.json
vendored
38
invokeai/frontend/web/dist/locales/ru.json
vendored
@ -46,7 +46,15 @@
|
||||
"statusLoadingModel": "Загрузка модели",
|
||||
"statusModelChanged": "Модель изменена",
|
||||
"githubLabel": "Github",
|
||||
"discordLabel": "Discord"
|
||||
"discordLabel": "Discord",
|
||||
"statusMergingModels": "Слияние моделей",
|
||||
"statusModelConverted": "Модель сконвертирована",
|
||||
"statusMergedModels": "Модели объединены",
|
||||
"pinOptionsPanel": "Закрепить панель настроек",
|
||||
"loading": "Загрузка",
|
||||
"loadingInvokeAI": "Загрузка Invoke AI",
|
||||
"back": "Назад",
|
||||
"statusConvertingModel": "Конвертация модели"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Генерации",
|
||||
@ -323,7 +331,30 @@
|
||||
"deleteConfig": "Удалить конфигурацию",
|
||||
"deleteMsg1": "Вы точно хотите удалить модель из InvokeAI?",
|
||||
"deleteMsg2": "Это не удалит файл модели с диска. Позже вы можете добавить его снова.",
|
||||
"repoIDValidationMsg": "Онлайн-репозиторий модели"
|
||||
"repoIDValidationMsg": "Онлайн-репозиторий модели",
|
||||
"convertToDiffusersHelpText5": "Пожалуйста, убедитесь, что у вас достаточно места на диске. Модели обычно занимают 4 – 7 Гб.",
|
||||
"invokeAIFolder": "Каталог InvokeAI",
|
||||
"ignoreMismatch": "Игнорировать несоответствия между выбранными моделями",
|
||||
"addCheckpointModel": "Добавить модель Checkpoint/Safetensor",
|
||||
"formMessageDiffusersModelLocationDesc": "Укажите хотя бы одно.",
|
||||
"convertToDiffusersHelpText3": "Файл модели на диске НЕ будет удалён или изменён. Вы сможете заново добавить его в Model Manager при необходимости.",
|
||||
"vaeRepoID": "ID репозитория VAE",
|
||||
"mergedModelName": "Название объединенной модели",
|
||||
"checkpointModels": "Checkpoints",
|
||||
"allModels": "Все модели",
|
||||
"addDiffuserModel": "Добавить Diffusers",
|
||||
"repo_id": "ID репозитория",
|
||||
"formMessageDiffusersVAELocationDesc": "Если не указано, InvokeAI будет искать файл VAE рядом с моделью.",
|
||||
"convert": "Преобразовать",
|
||||
"convertToDiffusers": "Преобразовать в Diffusers",
|
||||
"convertToDiffusersHelpText1": "Модель будет преобразована в формат 🧨 Diffusers.",
|
||||
"convertToDiffusersHelpText4": "Это единоразовое действие. Оно может занять 30—60 секунд в зависимости от характеристик вашего компьютера.",
|
||||
"convertToDiffusersHelpText6": "Вы хотите преобразовать эту модель?",
|
||||
"statusConverting": "Преобразование",
|
||||
"modelConverted": "Модель преобразована",
|
||||
"invokeRoot": "Каталог InvokeAI",
|
||||
"modelsMerged": "Модели объединены",
|
||||
"mergeModels": "Объединить модели"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Изображения",
|
||||
@ -503,5 +534,8 @@
|
||||
"betaDarkenOutside": "Затемнить снаружи",
|
||||
"betaLimitToBox": "Ограничить выделением",
|
||||
"betaPreserveMasked": "Сохранять маскируемую область"
|
||||
},
|
||||
"accessibility": {
|
||||
"modelSelect": "Выбор модели"
|
||||
}
|
||||
}
|
||||
|
17
invokeai/frontend/web/dist/locales/zh_Hant.json
vendored
17
invokeai/frontend/web/dist/locales/zh_Hant.json
vendored
@ -19,6 +19,21 @@
|
||||
"discordLabel": "Discord",
|
||||
"nodesDesc": "使用Node生成圖像的系統正在開發中。敬請期待有關於這項功能的更新。",
|
||||
"reportBugLabel": "回報錯誤",
|
||||
"githubLabel": "GitHub"
|
||||
"githubLabel": "GitHub",
|
||||
"langKorean": "韓語",
|
||||
"langPortuguese": "葡萄牙語",
|
||||
"hotkeysLabel": "快捷鍵",
|
||||
"languagePickerLabel": "切換語言",
|
||||
"langDutch": "荷蘭語",
|
||||
"langFrench": "法語",
|
||||
"langGerman": "德語",
|
||||
"langItalian": "義大利語",
|
||||
"langJapanese": "日語",
|
||||
"langPolish": "波蘭語",
|
||||
"langBrPortuguese": "巴西葡萄牙語",
|
||||
"langRussian": "俄語",
|
||||
"langSpanish": "西班牙語",
|
||||
"text2img": "文字到圖像",
|
||||
"unifiedCanvas": "統一畫布"
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
"build": "yarn run lint && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint:madge": "madge --circular src/main.tsx",
|
||||
"lint:eslint": "eslint --max-warnings=0",
|
||||
"lint:eslint": "eslint --max-warnings=0 .",
|
||||
"lint:prettier": "prettier --check .",
|
||||
"lint:tsc": "tsc --noEmit",
|
||||
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:tsc && yarn run lint:madge",
|
||||
@ -52,8 +52,6 @@
|
||||
"i18next-http-backend": "^2.1.1",
|
||||
"konva": "^8.4.2",
|
||||
"lodash": "^4.17.21",
|
||||
"overlayscrollbars": "^2.1.0",
|
||||
"overlayscrollbars-react": "^0.5.0",
|
||||
"re-resizable": "^6.9.9",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
|
@ -9,8 +9,9 @@ import useToastWatcher from 'features/system/hooks/useToastWatcher';
|
||||
|
||||
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
|
||||
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
|
||||
import { Box, Grid, Portal } from '@chakra-ui/react';
|
||||
import { APP_HEIGHT, APP_PADDING, APP_WIDTH } from 'theme/util/constants';
|
||||
import { Box, Flex, Grid, Portal } from '@chakra-ui/react';
|
||||
import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants';
|
||||
import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
|
||||
|
||||
keepGUIAlive();
|
||||
|
||||
@ -18,32 +19,33 @@ const App = () => {
|
||||
useToastWatcher();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid w="100vw" h="100vh">
|
||||
<ImageUploader>
|
||||
<ProgressBar />
|
||||
<Grid
|
||||
gap={4}
|
||||
p={APP_PADDING}
|
||||
gridAutoRows="min-content auto"
|
||||
w={APP_WIDTH}
|
||||
h={APP_HEIGHT}
|
||||
>
|
||||
<SiteHeader />
|
||||
<Grid w="100vw" h="100vh">
|
||||
<ImageUploader>
|
||||
<ProgressBar />
|
||||
<Grid
|
||||
gap={4}
|
||||
p={4}
|
||||
gridAutoRows="min-content auto"
|
||||
w={APP_WIDTH}
|
||||
h={APP_HEIGHT}
|
||||
>
|
||||
<SiteHeader />
|
||||
<Flex gap={4} w="full" h="full">
|
||||
<InvokeTabs />
|
||||
</Grid>
|
||||
<Box>
|
||||
<Console />
|
||||
</Box>
|
||||
</ImageUploader>
|
||||
<Portal>
|
||||
<FloatingParametersPanelButtons />
|
||||
</Portal>
|
||||
<Portal>
|
||||
<FloatingGalleryButton />
|
||||
</Portal>
|
||||
</Grid>
|
||||
</>
|
||||
<ImageGalleryPanel />
|
||||
</Flex>
|
||||
</Grid>
|
||||
<Box>
|
||||
<Console />
|
||||
</Box>
|
||||
</ImageUploader>
|
||||
<Portal>
|
||||
<FloatingParametersPanelButtons />
|
||||
</Portal>
|
||||
<Portal>
|
||||
<FloatingGalleryButton />
|
||||
</Portal>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -18,8 +18,6 @@ import '@fontsource/inter/600.css';
|
||||
import '@fontsource/inter/700.css';
|
||||
import '@fontsource/inter/800.css';
|
||||
import '@fontsource/inter/900.css';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
import 'theme/overlayscrollbar.css';
|
||||
|
||||
type ThemeLocaleProviderProps = {
|
||||
children: ReactNode;
|
||||
|
@ -57,7 +57,6 @@ const galleryBlacklist = [
|
||||
'currentImage',
|
||||
'currentImageUuid',
|
||||
'shouldAutoSwitchToNewImages',
|
||||
'shouldHoldGalleryOpen',
|
||||
'intermediateImage',
|
||||
].map((blacklistItem) => `gallery.${blacklistItem}`);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Box, forwardRef, Icon } from '@chakra-ui/react';
|
||||
import { Feature } from 'app/features';
|
||||
import { memo } from 'react';
|
||||
import { IconType } from 'react-icons';
|
||||
import { MdHelp } from 'react-icons/md';
|
||||
import GuidePopover from './GuidePopover';
|
||||
@ -19,4 +20,4 @@ const GuideIcon = forwardRef(
|
||||
)
|
||||
);
|
||||
|
||||
export default GuideIcon;
|
||||
export default memo(GuideIcon);
|
||||
|
@ -11,7 +11,7 @@ import { Feature, useFeatureHelpInfo } from 'app/features';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { SystemState } from 'features/system/store/systemSlice';
|
||||
import { ReactElement } from 'react';
|
||||
import { memo, ReactElement } from 'react';
|
||||
|
||||
type GuideProps = {
|
||||
children: ReactElement;
|
||||
@ -46,4 +46,4 @@ const GuidePopover = ({ children, feature }: GuideProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default GuidePopover;
|
||||
export default memo(GuidePopover);
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
forwardRef,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { cloneElement, ReactElement, ReactNode, useRef } from 'react';
|
||||
import { cloneElement, memo, ReactElement, ReactNode, useRef } from 'react';
|
||||
import IAIButton from './IAIButton';
|
||||
|
||||
type Props = {
|
||||
@ -79,4 +79,4 @@ const IAIAlertDialog = forwardRef((props: Props, ref) => {
|
||||
</>
|
||||
);
|
||||
});
|
||||
export default IAIAlertDialog;
|
||||
export default memo(IAIAlertDialog);
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
} from '@chakra-ui/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
export interface IAIButtonProps extends ButtonProps {
|
||||
tooltip?: string;
|
||||
@ -25,4 +25,4 @@ const IAIButton = forwardRef((props: IAIButtonProps, forwardedRef) => {
|
||||
);
|
||||
});
|
||||
|
||||
export default IAIButton;
|
||||
export default memo(IAIButton);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Checkbox, CheckboxProps } from '@chakra-ui/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
type IAICheckboxProps = CheckboxProps & {
|
||||
label: string | ReactNode;
|
||||
@ -14,4 +14,4 @@ const IAICheckbox = (props: IAICheckboxProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default IAICheckbox;
|
||||
export default memo(IAICheckbox);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { chakra, ChakraProps } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { RgbaColorPicker } from 'react-colorful';
|
||||
import { ColorPickerBaseProps, RgbaColor } from 'react-colorful/dist/types';
|
||||
|
||||
@ -35,4 +36,4 @@ const IAIColorPicker = (props: IAIColorPickerProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default IAIColorPicker;
|
||||
export default memo(IAIColorPicker);
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
} from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
|
||||
export type IAIIconButtonProps = IconButtonProps & {
|
||||
tooltip?: string;
|
||||
@ -33,4 +34,4 @@ const IAIIconButton = forwardRef((props: IAIIconButtonProps, forwardedRef) => {
|
||||
);
|
||||
});
|
||||
|
||||
export default IAIIconButton;
|
||||
export default memo(IAIIconButton);
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
Input,
|
||||
InputProps,
|
||||
} from '@chakra-ui/react';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { ChangeEvent, memo } from 'react';
|
||||
|
||||
interface IAIInputProps extends InputProps {
|
||||
label?: string;
|
||||
@ -15,7 +15,7 @@ interface IAIInputProps extends InputProps {
|
||||
formControlProps?: Omit<FormControlProps, 'isInvalid' | 'isDisabled'>;
|
||||
}
|
||||
|
||||
export default function IAIInput(props: IAIInputProps) {
|
||||
const IAIInput = (props: IAIInputProps) => {
|
||||
const {
|
||||
label = '',
|
||||
isDisabled = false,
|
||||
@ -34,4 +34,6 @@ export default function IAIInput(props: IAIInputProps) {
|
||||
<Input {...rest} />
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(IAIInput);
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { clamp } from 'lodash';
|
||||
|
||||
import { FocusEvent, useEffect, useState } from 'react';
|
||||
import { FocusEvent, memo, useEffect, useState } from 'react';
|
||||
|
||||
const numberStringRegex = /^-?(0\.)?\.?$/;
|
||||
|
||||
@ -139,4 +139,4 @@ const IAINumberInput = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default IAINumberInput;
|
||||
export default memo(IAINumberInput);
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
PopoverProps,
|
||||
PopoverTrigger,
|
||||
} from '@chakra-ui/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
type IAIPopoverProps = PopoverProps & {
|
||||
triggerComponent: ReactNode;
|
||||
@ -35,4 +35,4 @@ const IAIPopover = (props: IAIPopoverProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default IAIPopover;
|
||||
export default memo(IAIPopover);
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
} from '@chakra-ui/react';
|
||||
import { MouseEvent } from 'react';
|
||||
import { memo, MouseEvent } from 'react';
|
||||
|
||||
type IAISelectProps = SelectProps & {
|
||||
label?: string;
|
||||
@ -52,4 +52,4 @@ const IAISelect = (props: IAISelectProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default IAISelect;
|
||||
export default memo(IAISelect);
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
IconButtonProps,
|
||||
ButtonProps,
|
||||
} from '@chakra-ui/react';
|
||||
import { MouseEventHandler, ReactNode } from 'react';
|
||||
import { memo, MouseEventHandler, ReactNode } from 'react';
|
||||
import { MdArrowDropDown, MdArrowDropUp } from 'react-icons/md';
|
||||
|
||||
interface IAIMenuItem {
|
||||
@ -31,7 +31,7 @@ interface IAIMenuProps {
|
||||
menuItemProps?: MenuItemProps;
|
||||
}
|
||||
|
||||
export default function IAISimpleMenu(props: IAIMenuProps) {
|
||||
const IAISimpleMenu = (props: IAIMenuProps) => {
|
||||
const {
|
||||
menuType = 'icon',
|
||||
iconTooltip,
|
||||
@ -83,4 +83,6 @@ export default function IAISimpleMenu(props: IAIMenuProps) {
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(IAISimpleMenu);
|
||||
|
@ -25,8 +25,8 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { clamp } from 'lodash';
|
||||
|
||||
import { FocusEvent, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FocusEvent, memo, useEffect, useMemo, useState } from 'react';
|
||||
import { BiReset } from 'react-icons/bi';
|
||||
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
|
||||
|
||||
@ -62,7 +62,7 @@ export type IAIFullSliderProps = {
|
||||
sliderIAIIconButtonProps?: IAIIconButtonProps;
|
||||
};
|
||||
|
||||
export default function IAISlider(props: IAIFullSliderProps) {
|
||||
const IAISlider = (props: IAIFullSliderProps) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const {
|
||||
label,
|
||||
@ -174,16 +174,22 @@ export default function IAISlider(props: IAIFullSliderProps) {
|
||||
<>
|
||||
<SliderMark
|
||||
value={min}
|
||||
insetInlineStart={0}
|
||||
sx={{ insetInlineStart: 'unset !important' }}
|
||||
// insetInlineStart={0}
|
||||
sx={{
|
||||
insetInlineStart: '0 !important',
|
||||
insetInlineEnd: 'unset !important',
|
||||
}}
|
||||
{...sliderMarkProps}
|
||||
>
|
||||
{min}
|
||||
</SliderMark>
|
||||
<SliderMark
|
||||
value={max}
|
||||
insetInlineEnd={0}
|
||||
sx={{ insetInlineStart: 'unset !important' }}
|
||||
// insetInlineEnd={0}
|
||||
sx={{
|
||||
insetInlineStart: 'unset !important',
|
||||
insetInlineEnd: '0 !important',
|
||||
}}
|
||||
{...sliderMarkProps}
|
||||
>
|
||||
{max}
|
||||
@ -248,4 +254,6 @@ export default function IAISlider(props: IAIFullSliderProps) {
|
||||
</HStack>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(IAISlider);
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
Switch,
|
||||
SwitchProps,
|
||||
} from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
|
||||
interface Props extends SwitchProps {
|
||||
label?: string;
|
||||
@ -44,4 +45,4 @@ const IAISwitch = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default IAISwitch;
|
||||
export default memo(IAISwitch);
|
||||
|
@ -7,6 +7,7 @@ import { tabDict } from 'features/ui/components/InvokeTabs';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import {
|
||||
KeyboardEvent,
|
||||
memo,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -161,4 +162,4 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageUploader;
|
||||
export default memo(ImageUploader);
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { AppDispatch, AppGetState } from 'app/store';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { debounce } from 'lodash';
|
||||
import { setDoesCanvasNeedScaling } from '../canvasSlice';
|
||||
|
||||
const debouncedCanvasScale = debounce((dispatch: AppDispatch) => {
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
}, 300);
|
||||
|
||||
export const requestCanvasRescale =
|
||||
() => (dispatch: AppDispatch, getState: AppGetState) => {
|
||||
const activeTabName = activeTabNameSelector(getState());
|
||||
if (activeTabName === 'unifiedCanvas') {
|
||||
debouncedCanvasScale(dispatch);
|
||||
}
|
||||
};
|
@ -7,10 +7,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import {
|
||||
setDoesCanvasNeedScaling,
|
||||
setInitialCanvasImage,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||
import { GalleryState } from 'features/gallery/store/gallerySlice';
|
||||
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
|
||||
@ -52,6 +49,7 @@ import { gallerySelector } from '../store/gallerySelectors';
|
||||
import DeleteImageModal from './DeleteImageModal';
|
||||
import { useCallback } from 'react';
|
||||
import useSetBothPrompts from 'features/parameters/hooks/usePrompt';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
|
||||
const currentImageButtonsSelector = createSelector(
|
||||
[
|
||||
@ -361,7 +359,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
if (isLightboxOpen) dispatch(setIsLightboxOpen(false));
|
||||
|
||||
dispatch(setInitialCanvasImage(currentImage));
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
dispatch(requestCanvasRescale());
|
||||
|
||||
if (activeTabName !== 'unifiedCanvas') {
|
||||
dispatch(setActiveTab('unifiedCanvas'));
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,254 @@
|
||||
import { ButtonGroup, Flex, Grid, Icon } from '@chakra-ui/react';
|
||||
import { requestImages } from 'app/socketio/actions';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { imageGallerySelector } from 'features/gallery/store/gallerySelectors';
|
||||
import {
|
||||
setCurrentCategory,
|
||||
setGalleryImageMinimumWidth,
|
||||
setGalleryImageObjectFit,
|
||||
setShouldAutoSwitchToNewImages,
|
||||
setShouldUseSingleGalleryColumn,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
||||
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
||||
import { FaImage, FaUser, FaWrench } from 'react-icons/fa';
|
||||
import { MdPhotoLibrary } from 'react-icons/md';
|
||||
import HoverableImage from './HoverableImage';
|
||||
|
||||
import Scrollable from 'features/ui/components/common/Scrollable';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
|
||||
const GALLERY_SHOW_BUTTONS_MIN_WIDTH = 290;
|
||||
|
||||
const ImageGalleryContent = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const resizeObserverRef = useRef<HTMLDivElement>(null);
|
||||
const [shouldShouldIconButtons, setShouldShouldIconButtons] = useState(false);
|
||||
|
||||
const {
|
||||
images,
|
||||
currentCategory,
|
||||
currentImageUuid,
|
||||
shouldPinGallery,
|
||||
galleryImageMinimumWidth,
|
||||
galleryGridTemplateColumns,
|
||||
galleryImageObjectFit,
|
||||
shouldAutoSwitchToNewImages,
|
||||
areMoreImagesAvailable,
|
||||
shouldUseSingleGalleryColumn,
|
||||
} = useAppSelector(imageGallerySelector);
|
||||
|
||||
const handleClickLoadMore = () => {
|
||||
dispatch(requestImages(currentCategory));
|
||||
};
|
||||
|
||||
const handleChangeGalleryImageMinimumWidth = (v: number) => {
|
||||
dispatch(setGalleryImageMinimumWidth(v));
|
||||
};
|
||||
|
||||
const handleSetShouldPinGallery = () => {
|
||||
dispatch(togglePinGalleryPanel());
|
||||
dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!resizeObserverRef.current) {
|
||||
return;
|
||||
}
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (!resizeObserverRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
resizeObserverRef.current.clientWidth < GALLERY_SHOW_BUTTONS_MIN_WIDTH
|
||||
) {
|
||||
setShouldShouldIconButtons(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setShouldShouldIconButtons(false);
|
||||
});
|
||||
resizeObserver.observe(resizeObserverRef.current);
|
||||
return () => resizeObserver.disconnect(); // clean up
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" w="full" h="full" gap={4}>
|
||||
<Flex
|
||||
ref={resizeObserverRef}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<ButtonGroup
|
||||
size="sm"
|
||||
isAttached
|
||||
w="max-content"
|
||||
justifyContent="stretch"
|
||||
>
|
||||
{shouldShouldIconButtons ? (
|
||||
<>
|
||||
<IAIIconButton
|
||||
aria-label={t('gallery.showGenerations')}
|
||||
tooltip={t('gallery.showGenerations')}
|
||||
isChecked={currentCategory === 'result'}
|
||||
icon={<FaImage />}
|
||||
onClick={() => dispatch(setCurrentCategory('result'))}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label={t('gallery.showUploads')}
|
||||
tooltip={t('gallery.showUploads')}
|
||||
isChecked={currentCategory === 'user'}
|
||||
icon={<FaUser />}
|
||||
onClick={() => dispatch(setCurrentCategory('user'))}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IAIButton
|
||||
size="sm"
|
||||
isChecked={currentCategory === 'result'}
|
||||
onClick={() => dispatch(setCurrentCategory('result'))}
|
||||
flexGrow={1}
|
||||
>
|
||||
{t('gallery.generations')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size="sm"
|
||||
isChecked={currentCategory === 'user'}
|
||||
onClick={() => dispatch(setCurrentCategory('user'))}
|
||||
flexGrow={1}
|
||||
>
|
||||
{t('gallery.uploads')}
|
||||
</IAIButton>
|
||||
</>
|
||||
)}
|
||||
</ButtonGroup>
|
||||
|
||||
<Flex gap={2}>
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
aria-label={t('gallery.gallerySettings')}
|
||||
icon={<FaWrench />}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<IAISlider
|
||||
value={galleryImageMinimumWidth}
|
||||
onChange={handleChangeGalleryImageMinimumWidth}
|
||||
min={32}
|
||||
max={256}
|
||||
hideTooltip={true}
|
||||
label={t('gallery.galleryImageSize')}
|
||||
withReset
|
||||
handleReset={() => dispatch(setGalleryImageMinimumWidth(64))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
label={t('gallery.maintainAspectRatio')}
|
||||
isChecked={galleryImageObjectFit === 'contain'}
|
||||
onChange={() =>
|
||||
dispatch(
|
||||
setGalleryImageObjectFit(
|
||||
galleryImageObjectFit === 'contain' ? 'cover' : 'contain'
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
label={t('gallery.autoSwitchNewImages')}
|
||||
isChecked={shouldAutoSwitchToNewImages}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldAutoSwitchToNewImages(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
label={t('gallery.singleColumnLayout')}
|
||||
isChecked={shouldUseSingleGalleryColumn}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldUseSingleGalleryColumn(e.target.checked))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</IAIPopover>
|
||||
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
aria-label={t('gallery.pinGallery')}
|
||||
tooltip={`${t('gallery.pinGallery')} (Shift+G)`}
|
||||
onClick={handleSetShouldPinGallery}
|
||||
icon={shouldPinGallery ? <BsPinAngleFill /> : <BsPinAngle />}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Scrollable>
|
||||
<Flex direction="column" gap={2} h="full">
|
||||
{images.length || areMoreImagesAvailable ? (
|
||||
<>
|
||||
<Grid
|
||||
gap={2}
|
||||
style={{ gridTemplateColumns: galleryGridTemplateColumns }}
|
||||
>
|
||||
{images.map((image) => {
|
||||
const { uuid } = image;
|
||||
const isSelected = currentImageUuid === uuid;
|
||||
return (
|
||||
<HoverableImage
|
||||
key={uuid}
|
||||
image={image}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
<IAIButton
|
||||
onClick={handleClickLoadMore}
|
||||
isDisabled={!areMoreImagesAvailable}
|
||||
flexShrink={0}
|
||||
>
|
||||
{areMoreImagesAvailable
|
||||
? t('gallery.loadMore')
|
||||
: t('gallery.allImagesLoaded')}
|
||||
</IAIButton>
|
||||
</>
|
||||
) : (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 2,
|
||||
padding: 8,
|
||||
h: '100%',
|
||||
w: '100%',
|
||||
color: 'base.500',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
as={MdPhotoLibrary}
|
||||
sx={{
|
||||
w: 16,
|
||||
h: 16,
|
||||
}}
|
||||
/>
|
||||
<p>{t('gallery.noImagesInGallery')}</p>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Scrollable>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageGalleryContent;
|
@ -0,0 +1,191 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import { gallerySelector } from 'features/gallery/store/gallerySelectors';
|
||||
import {
|
||||
selectNextImage,
|
||||
selectPrevImage,
|
||||
setGalleryImageMinimumWidth,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
|
||||
import { clamp, isEqual } from 'lodash';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import './ImageGallery.css';
|
||||
import ImageGalleryContent from './ImageGalleryContent';
|
||||
import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer';
|
||||
import {
|
||||
setShouldShowGallery,
|
||||
toggleGalleryPanel,
|
||||
togglePinGalleryPanel,
|
||||
} from 'features/ui/store/uiSlice';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import {
|
||||
activeTabNameSelector,
|
||||
uiSelector,
|
||||
} from 'features/ui/store/uiSelectors';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
|
||||
const GALLERY_TAB_WIDTHS: Record<
|
||||
InvokeTabName,
|
||||
{ galleryMinWidth: number; galleryMaxWidth: number }
|
||||
> = {
|
||||
txt2img: { galleryMinWidth: 200, galleryMaxWidth: 500 },
|
||||
img2img: { galleryMinWidth: 200, galleryMaxWidth: 500 },
|
||||
unifiedCanvas: { galleryMinWidth: 200, galleryMaxWidth: 200 },
|
||||
nodes: { galleryMinWidth: 200, galleryMaxWidth: 500 },
|
||||
postprocess: { galleryMinWidth: 200, galleryMaxWidth: 500 },
|
||||
training: { galleryMinWidth: 200, galleryMaxWidth: 500 },
|
||||
};
|
||||
|
||||
const galleryPanelSelector = createSelector(
|
||||
[activeTabNameSelector, uiSelector, gallerySelector, isStagingSelector],
|
||||
(activeTabName, ui, gallery, isStaging) => {
|
||||
const { shouldPinGallery, shouldShowGallery } = ui;
|
||||
const { galleryImageMinimumWidth } = gallery;
|
||||
|
||||
return {
|
||||
activeTabName,
|
||||
isStaging,
|
||||
shouldPinGallery,
|
||||
shouldShowGallery,
|
||||
galleryImageMinimumWidth,
|
||||
isResizable: activeTabName !== 'unifiedCanvas',
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default function ImageGalleryPanel() {
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
shouldPinGallery,
|
||||
shouldShowGallery,
|
||||
galleryImageMinimumWidth,
|
||||
activeTabName,
|
||||
isStaging,
|
||||
isResizable,
|
||||
} = useAppSelector(galleryPanelSelector);
|
||||
|
||||
const handleSetShouldPinGallery = () => {
|
||||
dispatch(togglePinGalleryPanel());
|
||||
dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
const handleToggleGallery = () => {
|
||||
dispatch(toggleGalleryPanel());
|
||||
};
|
||||
|
||||
const handleCloseGallery = () => {
|
||||
dispatch(setShouldShowGallery(false));
|
||||
shouldPinGallery && dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
useHotkeys(
|
||||
'g',
|
||||
() => {
|
||||
handleToggleGallery();
|
||||
},
|
||||
[shouldShowGallery, shouldPinGallery]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'left',
|
||||
() => {
|
||||
dispatch(selectPrevImage());
|
||||
},
|
||||
{
|
||||
enabled: !isStaging || activeTabName !== 'unifiedCanvas',
|
||||
},
|
||||
[isStaging, activeTabName]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'right',
|
||||
() => {
|
||||
dispatch(selectNextImage());
|
||||
},
|
||||
{
|
||||
enabled: !isStaging || activeTabName !== 'unifiedCanvas',
|
||||
},
|
||||
[isStaging, activeTabName]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'shift+g',
|
||||
() => {
|
||||
handleSetShouldPinGallery();
|
||||
},
|
||||
[shouldPinGallery]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
dispatch(setShouldShowGallery(false));
|
||||
},
|
||||
{
|
||||
enabled: () => !shouldPinGallery,
|
||||
preventDefault: true,
|
||||
},
|
||||
[shouldPinGallery]
|
||||
);
|
||||
|
||||
const IMAGE_SIZE_STEP = 32;
|
||||
|
||||
useHotkeys(
|
||||
'shift+up',
|
||||
() => {
|
||||
if (galleryImageMinimumWidth < 256) {
|
||||
const newMinWidth = clamp(
|
||||
galleryImageMinimumWidth + IMAGE_SIZE_STEP,
|
||||
32,
|
||||
256
|
||||
);
|
||||
dispatch(setGalleryImageMinimumWidth(newMinWidth));
|
||||
}
|
||||
},
|
||||
[galleryImageMinimumWidth]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'shift+down',
|
||||
() => {
|
||||
if (galleryImageMinimumWidth > 32) {
|
||||
const newMinWidth = clamp(
|
||||
galleryImageMinimumWidth - IMAGE_SIZE_STEP,
|
||||
32,
|
||||
256
|
||||
);
|
||||
dispatch(setGalleryImageMinimumWidth(newMinWidth));
|
||||
}
|
||||
},
|
||||
[galleryImageMinimumWidth]
|
||||
);
|
||||
|
||||
return (
|
||||
<ResizableDrawer
|
||||
direction="right"
|
||||
isResizable={isResizable || !shouldPinGallery}
|
||||
isOpen={shouldShowGallery}
|
||||
onClose={handleCloseGallery}
|
||||
isPinned={shouldPinGallery}
|
||||
minWidth={
|
||||
shouldPinGallery
|
||||
? GALLERY_TAB_WIDTHS[activeTabName].galleryMinWidth
|
||||
: 200
|
||||
}
|
||||
maxWidth={
|
||||
shouldPinGallery
|
||||
? GALLERY_TAB_WIDTHS[activeTabName].galleryMaxWidth
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<ImageGalleryContent />
|
||||
</ResizableDrawer>
|
||||
);
|
||||
}
|
@ -1,53 +1,47 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import {
|
||||
activeTabNameSelector,
|
||||
uiSelector,
|
||||
} from 'features/ui/store/uiSelectors';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { GalleryState } from './gallerySlice';
|
||||
|
||||
export const gallerySelector = (state: RootState) => state.gallery;
|
||||
|
||||
export const imageGallerySelector = createSelector(
|
||||
[gallerySelector, lightboxSelector, isStagingSelector, activeTabNameSelector],
|
||||
(gallery: GalleryState, lightbox, isStaging, activeTabName) => {
|
||||
[gallerySelector, uiSelector, lightboxSelector, activeTabNameSelector],
|
||||
(gallery, ui, lightbox, activeTabName) => {
|
||||
const {
|
||||
categories,
|
||||
currentCategory,
|
||||
currentImageUuid,
|
||||
shouldPinGallery,
|
||||
shouldShowGallery,
|
||||
galleryImageMinimumWidth,
|
||||
galleryImageObjectFit,
|
||||
shouldHoldGalleryOpen,
|
||||
shouldAutoSwitchToNewImages,
|
||||
galleryWidth,
|
||||
shouldUseSingleGalleryColumn,
|
||||
} = gallery;
|
||||
|
||||
const { shouldPinGallery } = ui;
|
||||
|
||||
const { isLightboxOpen } = lightbox;
|
||||
|
||||
return {
|
||||
currentImageUuid,
|
||||
shouldPinGallery,
|
||||
shouldShowGallery,
|
||||
galleryImageMinimumWidth,
|
||||
galleryImageObjectFit,
|
||||
galleryGridTemplateColumns: shouldUseSingleGalleryColumn
|
||||
? 'auto'
|
||||
: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, auto))`,
|
||||
activeTabName,
|
||||
shouldHoldGalleryOpen,
|
||||
shouldAutoSwitchToNewImages,
|
||||
currentCategory,
|
||||
images: categories[currentCategory].images,
|
||||
areMoreImagesAvailable:
|
||||
categories[currentCategory].areMoreImagesAvailable,
|
||||
currentCategory,
|
||||
galleryWidth,
|
||||
isLightboxOpen,
|
||||
isStaging,
|
||||
shouldEnableResize:
|
||||
isLightboxOpen ||
|
||||
(activeTabName === 'unifiedCanvas' && shouldPinGallery)
|
||||
@ -65,7 +59,7 @@ export const imageGallerySelector = createSelector(
|
||||
|
||||
export const hoverableImageSelector = createSelector(
|
||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||
(gallery: GalleryState, system, lightbox, activeTabName) => {
|
||||
(gallery, system, lightbox, activeTabName) => {
|
||||
return {
|
||||
mayDeleteImage: system.isConnected && !system.isProcessing,
|
||||
galleryImageObjectFit: gallery.galleryImageObjectFit,
|
||||
|
@ -29,11 +29,8 @@ export interface GalleryState {
|
||||
boundingBox?: IRect;
|
||||
generationMode?: InvokeTabName;
|
||||
};
|
||||
shouldPinGallery: boolean;
|
||||
shouldShowGallery: boolean;
|
||||
galleryImageMinimumWidth: number;
|
||||
galleryImageObjectFit: GalleryImageObjectFitType;
|
||||
shouldHoldGalleryOpen: boolean;
|
||||
shouldAutoSwitchToNewImages: boolean;
|
||||
categories: {
|
||||
user: Gallery;
|
||||
@ -46,11 +43,8 @@ export interface GalleryState {
|
||||
|
||||
const initialState: GalleryState = {
|
||||
currentImageUuid: '',
|
||||
shouldPinGallery: true,
|
||||
shouldShowGallery: true,
|
||||
galleryImageMinimumWidth: 64,
|
||||
galleryImageObjectFit: 'cover',
|
||||
shouldHoldGalleryOpen: false,
|
||||
shouldAutoSwitchToNewImages: true,
|
||||
currentCategory: 'result',
|
||||
categories: {
|
||||
@ -233,13 +227,6 @@ export const gallerySlice = createSlice({
|
||||
areMoreImagesAvailable;
|
||||
}
|
||||
},
|
||||
setShouldPinGallery: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldPinGallery = action.payload;
|
||||
},
|
||||
setShouldShowGallery: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldShowGallery = action.payload;
|
||||
},
|
||||
|
||||
setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => {
|
||||
state.galleryImageMinimumWidth = action.payload;
|
||||
},
|
||||
@ -249,9 +236,6 @@ export const gallerySlice = createSlice({
|
||||
) => {
|
||||
state.galleryImageObjectFit = action.payload;
|
||||
},
|
||||
setShouldHoldGalleryOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldHoldGalleryOpen = action.payload;
|
||||
},
|
||||
setShouldAutoSwitchToNewImages: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldAutoSwitchToNewImages = action.payload;
|
||||
},
|
||||
@ -279,11 +263,8 @@ export const {
|
||||
setIntermediateImage,
|
||||
selectNextImage,
|
||||
selectPrevImage,
|
||||
setShouldPinGallery,
|
||||
setShouldShowGallery,
|
||||
setGalleryImageMinimumWidth,
|
||||
setGalleryImageObjectFit,
|
||||
setShouldHoldGalleryOpen,
|
||||
setShouldAutoSwitchToNewImages,
|
||||
setCurrentCategory,
|
||||
setGalleryWidth,
|
||||
|
@ -4,7 +4,7 @@ import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import CurrentImageButtons from 'features/gallery/components/CurrentImageButtons';
|
||||
import ImageGallery from 'features/gallery/components/ImageGallery';
|
||||
import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
|
||||
import ImageMetadataViewer from 'features/gallery/components/ImageMetaDataViewer/ImageMetadataViewer';
|
||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||
import { gallerySelector } from 'features/gallery/store/gallerySelectors';
|
||||
@ -164,7 +164,7 @@ export default function Lightbox() {
|
||||
<CurrentImageButtons />
|
||||
</Box>
|
||||
</Grid>
|
||||
<ImageGallery />
|
||||
<ImageGalleryPanel />
|
||||
</Flex>
|
||||
</Box>
|
||||
</TransformWrapper>
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
setCancelType,
|
||||
} from 'features/system/store/systemSlice';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { useEffect, useCallback, memo } from 'react';
|
||||
import { ButtonSpinner, ButtonGroup } from '@chakra-ui/react';
|
||||
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
@ -44,9 +44,9 @@ interface CancelButtonProps {
|
||||
btnGroupWidth?: string | number;
|
||||
}
|
||||
|
||||
export default function CancelButton(
|
||||
const CancelButton = (
|
||||
props: CancelButtonProps & Omit<IAIIconButtonProps, 'aria-label'>
|
||||
) {
|
||||
) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { btnGroupWidth = 'auto', ...rest } = props;
|
||||
const {
|
||||
@ -146,4 +146,6 @@ export default function CancelButton(
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(CancelButton);
|
||||
|
@ -1,22 +1,38 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
|
||||
import { setShouldShowGallery } from 'features/gallery/store/gallerySlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import { setShouldShowGallery } from 'features/ui/store/uiSlice';
|
||||
import { isEqual } from 'lodash';
|
||||
import { MdPhotoLibrary } from 'react-icons/md';
|
||||
import { floatingSelector } from './FloatingParametersPanelButtons';
|
||||
import { activeTabNameSelector, uiSelector } from '../store/uiSelectors';
|
||||
|
||||
const floatingGalleryButtonSelector = createSelector(
|
||||
[activeTabNameSelector, uiSelector],
|
||||
(activeTabName, ui) => {
|
||||
const { shouldPinGallery } = ui;
|
||||
|
||||
return {
|
||||
shouldPinGallery,
|
||||
shouldShowGalleryButton:
|
||||
!shouldPinGallery &&
|
||||
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName),
|
||||
};
|
||||
},
|
||||
{ memoizeOptions: { resultEqualityCheck: isEqual } }
|
||||
);
|
||||
|
||||
const FloatingGalleryButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const { shouldPinGallery, shouldShowGalleryButton } = useAppSelector(
|
||||
floatingGalleryButtonSelector
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
const { shouldShowGalleryButton, shouldPinGallery } =
|
||||
useAppSelector(floatingSelector);
|
||||
|
||||
const handleShowGallery = () => {
|
||||
dispatch(setShouldShowGallery(true));
|
||||
if (shouldPinGallery) {
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
}
|
||||
shouldPinGallery && dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
return shouldShowGalleryButton ? (
|
||||
|
@ -2,9 +2,7 @@ import { ChakraProps, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
|
||||
import { gallerySelector } from 'features/gallery/store/gallerySelectors';
|
||||
import { GalleryState } from 'features/gallery/store/gallerySlice';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
|
||||
import InvokeButton from 'features/parameters/components/ProcessButtons/InvokeButton';
|
||||
import {
|
||||
@ -22,41 +20,26 @@ const floatingButtonStyles: ChakraProps['sx'] = {
|
||||
borderEndStartRadius: 0,
|
||||
};
|
||||
|
||||
export const floatingSelector = createSelector(
|
||||
[gallerySelector, uiSelector, activeTabNameSelector],
|
||||
(gallery: GalleryState, ui, activeTabName) => {
|
||||
const {
|
||||
shouldPinParametersPanel,
|
||||
shouldShowParametersPanel,
|
||||
shouldUseCanvasBetaLayout,
|
||||
} = ui;
|
||||
|
||||
const { shouldShowGallery, shouldPinGallery, shouldHoldGalleryOpen } =
|
||||
gallery;
|
||||
export const floatingParametersPanelButtonSelector = createSelector(
|
||||
[uiSelector, activeTabNameSelector],
|
||||
(ui, activeTabName) => {
|
||||
const { shouldPinParametersPanel, shouldUseCanvasBetaLayout } = ui;
|
||||
|
||||
const canvasBetaLayoutCheck =
|
||||
shouldUseCanvasBetaLayout && activeTabName === 'unifiedCanvas';
|
||||
|
||||
const shouldShowProcessButtons =
|
||||
!canvasBetaLayoutCheck && !shouldPinParametersPanel;
|
||||
|
||||
const shouldShowParametersPanelButton =
|
||||
!canvasBetaLayoutCheck &&
|
||||
!shouldPinParametersPanel &&
|
||||
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
|
||||
|
||||
const shouldShowGalleryButton =
|
||||
!(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) &&
|
||||
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
|
||||
|
||||
const shouldShowProcessButtons =
|
||||
!canvasBetaLayoutCheck && !shouldPinParametersPanel;
|
||||
|
||||
return {
|
||||
shouldPinParametersPanel,
|
||||
shouldShowProcessButtons,
|
||||
shouldShowParametersPanelButton,
|
||||
shouldShowParametersPanel,
|
||||
shouldShowGallery,
|
||||
shouldPinGallery,
|
||||
shouldShowGalleryButton,
|
||||
shouldShowProcessButtons,
|
||||
};
|
||||
},
|
||||
{ memoizeOptions: { resultEqualityCheck: isEqual } }
|
||||
@ -66,16 +49,14 @@ const FloatingParametersPanelButtons = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
shouldShowParametersPanelButton,
|
||||
shouldShowProcessButtons,
|
||||
shouldShowParametersPanelButton,
|
||||
shouldPinParametersPanel,
|
||||
} = useAppSelector(floatingSelector);
|
||||
} = useAppSelector(floatingParametersPanelButtonSelector);
|
||||
|
||||
const handleShowOptionsPanel = () => {
|
||||
dispatch(setShouldShowParametersPanel(true));
|
||||
if (shouldPinParametersPanel) {
|
||||
setTimeout(() => dispatch(setDoesCanvasNeedScaling(true)), 400);
|
||||
}
|
||||
shouldPinParametersPanel && dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
return shouldShowParametersPanelButton ? (
|
||||
|
@ -13,18 +13,11 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import NodesWIP from 'common/components/WorkInProgress/NodesWIP';
|
||||
import { PostProcessingWIP } from 'common/components/WorkInProgress/PostProcessingWIP';
|
||||
import TrainingWIP from 'common/components/WorkInProgress/Training';
|
||||
import useUpdateTranslations from 'common/hooks/useUpdateTranslations';
|
||||
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
|
||||
import { setShouldShowGallery } from 'features/gallery/store/gallerySlice';
|
||||
import Lightbox from 'features/lightbox/components/Lightbox';
|
||||
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
|
||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||
import {
|
||||
setActiveTab,
|
||||
setShouldShowParametersPanel,
|
||||
} from 'features/ui/store/uiSlice';
|
||||
import i18n from 'i18n';
|
||||
import { ReactElement } from 'react';
|
||||
import { setActiveTab, togglePanels } from 'features/ui/store/uiSlice';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import {
|
||||
MdDeviceHub,
|
||||
@ -35,59 +28,98 @@ import {
|
||||
MdTextFields,
|
||||
} from 'react-icons/md';
|
||||
import { activeTabIndexSelector } from '../store/uiSelectors';
|
||||
import { floatingSelector } from './FloatingParametersPanelButtons';
|
||||
import ImageToImageWorkarea from 'features/ui/components/tabs/ImageToImage/ImageToImageWorkarea';
|
||||
import TextToImageWorkarea from 'features/ui/components/tabs/TextToImage/TextToImageWorkarea';
|
||||
import UnifiedCanvasWorkarea from 'features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ResourceKey } from 'i18next';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
|
||||
export interface InvokeTabInfo {
|
||||
title: ReactElement;
|
||||
workarea: ReactElement;
|
||||
id: string;
|
||||
icon: ReactNode;
|
||||
workarea: ReactNode;
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
export const tabDict: Record<InvokeTabName, InvokeTabInfo> = {
|
||||
const tabInfo: InvokeTabInfo[] = [
|
||||
{
|
||||
id: 'text2img',
|
||||
icon: <Icon as={MdTextFields} boxSize={6} />,
|
||||
workarea: <TextToImageWorkarea />,
|
||||
tooltip: 'Text To Image',
|
||||
},
|
||||
{
|
||||
id: 'img2img',
|
||||
icon: <Icon as={MdPhotoLibrary} boxSize={6} />,
|
||||
workarea: <ImageToImageWorkarea />,
|
||||
tooltip: 'Image To Image',
|
||||
},
|
||||
{
|
||||
id: 'unifiedCanvas',
|
||||
icon: <Icon as={MdGridOn} boxSize={6} />,
|
||||
workarea: <UnifiedCanvasWorkarea />,
|
||||
tooltip: 'Unified Canvas',
|
||||
},
|
||||
{
|
||||
id: 'nodes',
|
||||
icon: <Icon as={MdDeviceHub} boxSize={6} />,
|
||||
workarea: <NodesWIP />,
|
||||
tooltip: 'Nodes',
|
||||
},
|
||||
{
|
||||
id: 'postProcessing',
|
||||
icon: <Icon as={MdPhotoFilter} boxSize={6} />,
|
||||
workarea: <PostProcessingWIP />,
|
||||
tooltip: 'Post Processing',
|
||||
},
|
||||
{
|
||||
id: 'training',
|
||||
icon: <Icon as={MdFlashOn} boxSize={6} />,
|
||||
workarea: <TrainingWIP />,
|
||||
tooltip: 'Training',
|
||||
},
|
||||
];
|
||||
|
||||
export interface InvokeTabInfo2 {
|
||||
icon: ReactNode;
|
||||
workarea: ReactNode;
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
export const tabDict: Record<InvokeTabName, InvokeTabInfo2> = {
|
||||
txt2img: {
|
||||
title: <Icon as={MdTextFields} boxSize={6} />,
|
||||
icon: <Icon as={MdTextFields} boxSize={6} />,
|
||||
workarea: <TextToImageWorkarea />,
|
||||
tooltip: 'Text To Image',
|
||||
},
|
||||
img2img: {
|
||||
title: <Icon as={MdPhotoLibrary} boxSize={6} />,
|
||||
icon: <Icon as={MdPhotoLibrary} boxSize={6} />,
|
||||
workarea: <ImageToImageWorkarea />,
|
||||
tooltip: 'Image To Image',
|
||||
},
|
||||
unifiedCanvas: {
|
||||
title: <Icon as={MdGridOn} boxSize={6} />,
|
||||
icon: <Icon as={MdGridOn} boxSize={6} />,
|
||||
workarea: <UnifiedCanvasWorkarea />,
|
||||
tooltip: 'Unified Canvas',
|
||||
},
|
||||
nodes: {
|
||||
title: <Icon as={MdDeviceHub} boxSize={6} />,
|
||||
icon: <Icon as={MdDeviceHub} boxSize={6} />,
|
||||
workarea: <NodesWIP />,
|
||||
tooltip: 'Nodes',
|
||||
},
|
||||
postprocess: {
|
||||
title: <Icon as={MdPhotoFilter} boxSize={6} />,
|
||||
icon: <Icon as={MdPhotoFilter} boxSize={6} />,
|
||||
workarea: <PostProcessingWIP />,
|
||||
tooltip: 'Post Processing',
|
||||
},
|
||||
training: {
|
||||
title: <Icon as={MdFlashOn} boxSize={6} />,
|
||||
icon: <Icon as={MdFlashOn} boxSize={6} />,
|
||||
workarea: <TrainingWIP />,
|
||||
tooltip: 'Training',
|
||||
},
|
||||
};
|
||||
|
||||
function updateTabTranslations() {
|
||||
tabDict.txt2img.tooltip = i18n.t('common.text2img');
|
||||
tabDict.img2img.tooltip = i18n.t('common.img2img');
|
||||
tabDict.unifiedCanvas.tooltip = i18n.t('common.unifiedCanvas');
|
||||
tabDict.nodes.tooltip = i18n.t('common.nodes');
|
||||
tabDict.postprocess.tooltip = i18n.t('common.postProcessing');
|
||||
tabDict.training.tooltip = i18n.t('common.training');
|
||||
}
|
||||
|
||||
export default function InvokeTabs() {
|
||||
const activeTab = useAppSelector(activeTabIndexSelector);
|
||||
|
||||
@ -95,14 +127,15 @@ export default function InvokeTabs() {
|
||||
(state: RootState) => state.lightbox.isLightboxOpen
|
||||
);
|
||||
|
||||
const {
|
||||
shouldShowGallery,
|
||||
shouldShowParametersPanel,
|
||||
shouldPinGallery,
|
||||
shouldPinParametersPanel,
|
||||
} = useAppSelector(floatingSelector);
|
||||
const shouldPinGallery = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldPinGallery
|
||||
);
|
||||
|
||||
useUpdateTranslations(updateTabTranslations);
|
||||
const shouldPinParametersPanel = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldPinParametersPanel
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -142,52 +175,36 @@ export default function InvokeTabs() {
|
||||
useHotkeys(
|
||||
'f',
|
||||
() => {
|
||||
if (shouldShowGallery || shouldShowParametersPanel) {
|
||||
dispatch(setShouldShowParametersPanel(false));
|
||||
dispatch(setShouldShowGallery(false));
|
||||
} else {
|
||||
dispatch(setShouldShowParametersPanel(true));
|
||||
dispatch(setShouldShowGallery(true));
|
||||
}
|
||||
if (shouldPinGallery || shouldPinParametersPanel)
|
||||
setTimeout(() => dispatch(setDoesCanvasNeedScaling(true)), 400);
|
||||
dispatch(togglePanels());
|
||||
(shouldPinGallery || shouldPinParametersPanel) &&
|
||||
dispatch(requestCanvasRescale());
|
||||
},
|
||||
[shouldShowGallery, shouldShowParametersPanel]
|
||||
[shouldPinGallery, shouldPinParametersPanel]
|
||||
);
|
||||
|
||||
const renderTabs = () => {
|
||||
const tabsToRender: ReactElement[] = [];
|
||||
Object.keys(tabDict).forEach((key) => {
|
||||
tabsToRender.push(
|
||||
const tabs = useMemo(
|
||||
() =>
|
||||
tabInfo.map((tab) => (
|
||||
<Tooltip
|
||||
key={key}
|
||||
key={tab.id}
|
||||
hasArrow
|
||||
label={tabDict[key as keyof typeof tabDict].tooltip}
|
||||
label={String(t(`common.${tab.id}` as ResourceKey))}
|
||||
placement="end"
|
||||
>
|
||||
<Tab>
|
||||
<VisuallyHidden>
|
||||
{tabDict[key as keyof typeof tabDict].tooltip}
|
||||
</VisuallyHidden>
|
||||
{tabDict[key as keyof typeof tabDict].title}
|
||||
<VisuallyHidden>{tab.tooltip}</VisuallyHidden>
|
||||
{tab.icon}
|
||||
</Tab>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
return tabsToRender;
|
||||
};
|
||||
)),
|
||||
[t]
|
||||
);
|
||||
|
||||
const renderTabPanels = () => {
|
||||
const tabPanelsToRender: ReactElement[] = [];
|
||||
Object.keys(tabDict).forEach((key) => {
|
||||
tabPanelsToRender.push(
|
||||
<TabPanel key={key}>
|
||||
{tabDict[key as keyof typeof tabDict].workarea}
|
||||
</TabPanel>
|
||||
);
|
||||
});
|
||||
return tabPanelsToRender;
|
||||
};
|
||||
const tabPanels = useMemo(
|
||||
() =>
|
||||
tabInfo.map((tab) => <TabPanel key={tab.id}>{tab.workarea}</TabPanel>),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
@ -197,9 +214,10 @@ export default function InvokeTabs() {
|
||||
onChange={(index: number) => {
|
||||
dispatch(setActiveTab(index));
|
||||
}}
|
||||
flexGrow={1}
|
||||
>
|
||||
<TabList>{renderTabs()}</TabList>
|
||||
<TabPanels>{isLightBoxOpen ? <Lightbox /> : renderTabPanels()}</TabPanels>
|
||||
<TabList>{tabs}</TabList>
|
||||
<TabPanels>{isLightBoxOpen ? <Lightbox /> : tabPanels}</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,66 @@
|
||||
import { Box, BoxProps, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import { setInitialImage } from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
activeTabNameSelector,
|
||||
uiSelector,
|
||||
} from 'features/ui/store/uiSelectors';
|
||||
import { DragEvent, ReactNode } from 'react';
|
||||
|
||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||
import useGetImageByUuid from 'features/gallery/hooks/useGetImageByUuid';
|
||||
import { isEqual } from 'lodash';
|
||||
import { APP_CONTENT_HEIGHT } from 'theme/util/constants';
|
||||
import ParametersPanel from './ParametersPanel';
|
||||
|
||||
const workareaSelector = createSelector(
|
||||
[uiSelector, activeTabNameSelector],
|
||||
(ui, activeTabName) => {
|
||||
const { shouldPinParametersPanel } = ui;
|
||||
return {
|
||||
shouldPinParametersPanel,
|
||||
activeTabName,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type InvokeWorkareaProps = BoxProps & {
|
||||
parametersPanelContent: ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
||||
const { parametersPanelContent, children, ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { activeTabName } = useAppSelector(workareaSelector);
|
||||
|
||||
const getImageByUuid = useGetImageByUuid();
|
||||
|
||||
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
|
||||
const uuid = e.dataTransfer.getData('invokeai/imageUuid');
|
||||
const image = getImageByUuid(uuid);
|
||||
if (!image) return;
|
||||
if (activeTabName === 'img2img') {
|
||||
dispatch(setInitialImage(image));
|
||||
} else if (activeTabName === 'unifiedCanvas') {
|
||||
dispatch(setInitialCanvasImage(image));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex {...rest} pos="relative" w="full" h={APP_CONTENT_HEIGHT} gap={4}>
|
||||
<ParametersPanel>{parametersPanelContent}</ParametersPanel>
|
||||
<Box pos="relative" w="100%" h="100%" onDrop={handleDrop}>
|
||||
{children}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvokeWorkarea;
|
@ -0,0 +1,104 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
||||
import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer';
|
||||
import {
|
||||
setShouldShowParametersPanel,
|
||||
toggleParametersPanel,
|
||||
togglePinParametersPanel,
|
||||
} from 'features/ui/store/uiSlice';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
|
||||
import Scrollable from './common/Scrollable';
|
||||
import PinParametersPanelButton from './PinParametersPanelButton';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
|
||||
type ParametersPanelProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const ParametersPanel = ({ children }: ParametersPanelProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const shouldPinParametersPanel = useAppSelector(
|
||||
(state) => state.ui.shouldPinParametersPanel
|
||||
);
|
||||
const shouldShowParametersPanel = useAppSelector(
|
||||
(state) => state.ui.shouldShowParametersPanel
|
||||
);
|
||||
|
||||
const closeParametersPanel = () => {
|
||||
dispatch(setShouldShowParametersPanel(false));
|
||||
};
|
||||
|
||||
useHotkeys(
|
||||
'o',
|
||||
() => {
|
||||
dispatch(toggleParametersPanel());
|
||||
shouldPinParametersPanel && dispatch(requestCanvasRescale());
|
||||
},
|
||||
[shouldPinParametersPanel]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
dispatch(setShouldShowParametersPanel(false));
|
||||
},
|
||||
{
|
||||
enabled: () => !shouldPinParametersPanel,
|
||||
preventDefault: true,
|
||||
},
|
||||
[shouldPinParametersPanel]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'shift+o',
|
||||
() => {
|
||||
dispatch(togglePinParametersPanel());
|
||||
dispatch(requestCanvasRescale());
|
||||
},
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<ResizableDrawer
|
||||
direction="left"
|
||||
isResizable={false}
|
||||
isOpen={shouldShowParametersPanel || shouldPinParametersPanel}
|
||||
onClose={closeParametersPanel}
|
||||
isPinned={shouldPinParametersPanel}
|
||||
sx={{
|
||||
borderColor: 'base.700',
|
||||
p: shouldPinParametersPanel ? 0 : 4,
|
||||
bg: 'base.900',
|
||||
}}
|
||||
initialWidth={PARAMETERS_PANEL_WIDTH}
|
||||
minWidth={PARAMETERS_PANEL_WIDTH}
|
||||
>
|
||||
<Flex flexDir="column" position="relative" h="full" w="full">
|
||||
{!shouldPinParametersPanel && (
|
||||
<Flex
|
||||
paddingTop={1.5}
|
||||
paddingBottom={4}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<InvokeAILogoComponent />
|
||||
<PinParametersPanelButton />
|
||||
</Flex>
|
||||
)}
|
||||
<Scrollable>{children}</Scrollable>
|
||||
{shouldPinParametersPanel && (
|
||||
<PinParametersPanelButton
|
||||
sx={{ position: 'absolute', top: 0, insetInlineEnd: 0 }}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</ResizableDrawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParametersPanel);
|
@ -1,14 +1,17 @@
|
||||
import { Box, Icon, Tooltip } from '@chakra-ui/react';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIIconButton, {
|
||||
IAIIconButtonProps,
|
||||
} from 'common/components/IAIIconButton';
|
||||
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
||||
import { setShouldPinParametersPanel } from '../../store/uiSlice';
|
||||
import { setShouldPinParametersPanel } from '../store/uiSlice';
|
||||
|
||||
const PinParametersPanelButton = () => {
|
||||
type PinParametersPanelButtonProps = Omit<IAIIconButtonProps, 'aria-label'>;
|
||||
|
||||
const PinParametersPanelButton = (props: PinParametersPanelButtonProps) => {
|
||||
const { sx } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const shouldPinParametersPanel = useAppSelector(
|
||||
(state) => state.ui.shouldPinParametersPanel
|
||||
@ -18,23 +21,27 @@ const PinParametersPanelButton = () => {
|
||||
|
||||
const handleClickPinOptionsPanel = () => {
|
||||
dispatch(setShouldPinParametersPanel(!shouldPinParametersPanel));
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip label={t('common.pinOptionsPanel')}>
|
||||
<IAIIconButton
|
||||
{...props}
|
||||
aria-label={t('common.pinOptionsPanel')}
|
||||
opacity={0.2}
|
||||
onClick={handleClickPinOptionsPanel}
|
||||
icon={shouldPinParametersPanel ? <BsPinAngleFill /> : <BsPinAngle />}
|
||||
variant="unstyled"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
padding={2}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 1,
|
||||
insetInlineEnd: 1,
|
||||
color: 'base.700',
|
||||
_hover: {
|
||||
color: 'base.550',
|
||||
},
|
||||
_active: {
|
||||
color: 'base.500',
|
||||
},
|
||||
...sx,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
@ -1,147 +0,0 @@
|
||||
import { Box, BoxProps, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import ImageGallery from 'features/gallery/components/ImageGallery';
|
||||
import { setInitialImage } from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
activeTabNameSelector,
|
||||
uiSelector,
|
||||
} from 'features/ui/store/uiSelectors';
|
||||
import { DragEvent, ReactNode } from 'react';
|
||||
|
||||
import {
|
||||
setDoesCanvasNeedScaling,
|
||||
setInitialCanvasImage,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import useGetImageByUuid from 'features/gallery/hooks/useGetImageByUuid';
|
||||
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||
import { isEqual } from 'lodash';
|
||||
import {
|
||||
APP_CONTENT_HEIGHT,
|
||||
PARAMETERS_PANEL_WIDTH,
|
||||
} from 'theme/util/constants';
|
||||
import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer';
|
||||
import {
|
||||
setShouldPinParametersPanel,
|
||||
setShouldShowParametersPanel,
|
||||
} from 'features/ui/store/uiSlice';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
|
||||
|
||||
const workareaSelector = createSelector(
|
||||
[uiSelector, lightboxSelector, activeTabNameSelector],
|
||||
(ui, lightbox, activeTabName) => {
|
||||
const { shouldPinParametersPanel } = ui;
|
||||
const { isLightboxOpen } = lightbox;
|
||||
return {
|
||||
shouldPinParametersPanel,
|
||||
isLightboxOpen,
|
||||
activeTabName,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type InvokeWorkareaProps = BoxProps & {
|
||||
parametersPanel: ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
||||
const { parametersPanel, children, ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { activeTabName, isLightboxOpen } = useAppSelector(workareaSelector);
|
||||
const { shouldPinParametersPanel, shouldShowParametersPanel } =
|
||||
useAppSelector(uiSelector);
|
||||
|
||||
const getImageByUuid = useGetImageByUuid();
|
||||
|
||||
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
|
||||
const uuid = e.dataTransfer.getData('invokeai/imageUuid');
|
||||
const image = getImageByUuid(uuid);
|
||||
if (!image) return;
|
||||
if (activeTabName === 'img2img') {
|
||||
dispatch(setInitialImage(image));
|
||||
} else if (activeTabName === 'unifiedCanvas') {
|
||||
dispatch(setInitialCanvasImage(image));
|
||||
}
|
||||
};
|
||||
|
||||
const closeParametersPanel = () => {
|
||||
dispatch(setShouldShowParametersPanel(false));
|
||||
};
|
||||
|
||||
useHotkeys(
|
||||
'o',
|
||||
() => {
|
||||
dispatch(setShouldShowParametersPanel(!shouldShowParametersPanel));
|
||||
shouldPinParametersPanel &&
|
||||
setTimeout(() => dispatch(setDoesCanvasNeedScaling(true)), 400);
|
||||
},
|
||||
[shouldShowParametersPanel, shouldPinParametersPanel]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
dispatch(setShouldShowParametersPanel(false));
|
||||
},
|
||||
{
|
||||
enabled: () => !shouldPinParametersPanel,
|
||||
preventDefault: true,
|
||||
},
|
||||
[shouldPinParametersPanel]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'shift+o',
|
||||
() => {
|
||||
dispatch(setShouldPinParametersPanel(!shouldPinParametersPanel));
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
},
|
||||
[shouldPinParametersPanel]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex {...rest} pos="relative" h={APP_CONTENT_HEIGHT} gap={4}>
|
||||
<ResizableDrawer
|
||||
direction="left"
|
||||
isResizable={true}
|
||||
shouldAllowResize={!shouldPinParametersPanel}
|
||||
isOpen={shouldShowParametersPanel || shouldPinParametersPanel}
|
||||
onClose={closeParametersPanel}
|
||||
isPinned={shouldPinParametersPanel}
|
||||
handleWidth={5}
|
||||
handleInteractWidth={'15px'}
|
||||
sx={{
|
||||
borderColor: 'base.700',
|
||||
p: shouldPinParametersPanel ? 0 : 4,
|
||||
bg: 'base.900',
|
||||
}}
|
||||
initialWidth={PARAMETERS_PANEL_WIDTH}
|
||||
minWidth={PARAMETERS_PANEL_WIDTH}
|
||||
pinnedWidth={PARAMETERS_PANEL_WIDTH}
|
||||
pinnedHeight={APP_CONTENT_HEIGHT}
|
||||
>
|
||||
<Flex
|
||||
flexDir="column"
|
||||
rowGap={4}
|
||||
paddingTop={!shouldPinParametersPanel ? 1.5 : 0}
|
||||
>
|
||||
{!shouldPinParametersPanel && <InvokeAILogoComponent />}
|
||||
{parametersPanel}
|
||||
</Flex>
|
||||
</ResizableDrawer>
|
||||
<Box pos="relative" w="100%" h="100%" onDrop={handleDrop}>
|
||||
{children}
|
||||
</Box>
|
||||
{!isLightboxOpen && <ImageGallery />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvokeWorkarea;
|
@ -16,13 +16,13 @@ import {
|
||||
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { LangDirection } from './types';
|
||||
import {
|
||||
getDefaultSize,
|
||||
getHandleEnables,
|
||||
getHandleStyles,
|
||||
getMinMaxDimensions,
|
||||
getResizableStyles,
|
||||
getSlideDirection,
|
||||
parseAndPadSize,
|
||||
} from './util';
|
||||
import Scrollable from '../Scrollable';
|
||||
|
||||
type ResizableDrawerProps = ResizableProps & {
|
||||
children: ReactNode;
|
||||
@ -31,21 +31,18 @@ type ResizableDrawerProps = ResizableProps & {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
direction?: SlideDirection;
|
||||
initialWidth?: string | number;
|
||||
minWidth?: string | number;
|
||||
maxWidth?: string | number;
|
||||
initialHeight?: string | number;
|
||||
minHeight?: string | number;
|
||||
maxHeight?: string | number;
|
||||
shouldAllowResize?: boolean;
|
||||
initialWidth?: number;
|
||||
minWidth?: number;
|
||||
maxWidth?: number;
|
||||
initialHeight?: number;
|
||||
minHeight?: number;
|
||||
maxHeight?: number;
|
||||
onResizeStart?: ResizeStartCallback;
|
||||
onResizeStop?: ResizeCallback;
|
||||
onResize?: ResizeCallback;
|
||||
handleWidth?: number;
|
||||
handleWidth?: string | number;
|
||||
handleInteractWidth?: string | number;
|
||||
sx?: ChakraProps['sx'];
|
||||
pinnedWidth: number;
|
||||
pinnedHeight: string | number;
|
||||
};
|
||||
|
||||
const ChakraResizeable = chakra(Resizable, {
|
||||
@ -59,26 +56,35 @@ const ResizableDrawer = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
children,
|
||||
initialWidth = undefined,
|
||||
minWidth = undefined,
|
||||
maxWidth = undefined,
|
||||
initialHeight = undefined,
|
||||
minHeight = undefined,
|
||||
maxHeight = undefined,
|
||||
shouldAllowResize,
|
||||
initialWidth,
|
||||
minWidth,
|
||||
maxWidth,
|
||||
initialHeight,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
onResizeStart,
|
||||
onResizeStop,
|
||||
onResize,
|
||||
handleWidth = 5,
|
||||
handleWidth = '5px',
|
||||
handleInteractWidth = '15px',
|
||||
pinnedWidth,
|
||||
pinnedHeight,
|
||||
sx = {},
|
||||
}: ResizableDrawerProps) => {
|
||||
const langDirection = useTheme().direction as LangDirection;
|
||||
|
||||
const outsideClickRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [width, setWidth] = useState<number | string>(
|
||||
initialWidth ??
|
||||
minWidth ??
|
||||
(['left', 'right'].includes(direction) ? 500 : '100vw')
|
||||
);
|
||||
|
||||
const [height, setHeight] = useState<number | string>(
|
||||
initialHeight ??
|
||||
minHeight ??
|
||||
(['top', 'bottom'].includes(direction) ? 500 : '100vh')
|
||||
);
|
||||
|
||||
useOutsideClick({
|
||||
ref: outsideClickRef,
|
||||
handler: () => {
|
||||
@ -90,15 +96,9 @@ const ResizableDrawer = ({
|
||||
},
|
||||
});
|
||||
|
||||
const [width, setWidth] = useState<number | string>(0);
|
||||
const [height, setHeight] = useState<number | string>(0);
|
||||
|
||||
const handleEnables = useMemo(
|
||||
() =>
|
||||
isResizable && shouldAllowResize
|
||||
? getHandleEnables({ direction, langDirection })
|
||||
: {},
|
||||
[isResizable, shouldAllowResize, langDirection, direction]
|
||||
() => (isResizable ? getHandleEnables({ direction, langDirection }) : {}),
|
||||
[isResizable, langDirection, direction]
|
||||
);
|
||||
|
||||
const handleStyles = useMemo(
|
||||
@ -116,29 +116,31 @@ const ResizableDrawer = ({
|
||||
() =>
|
||||
getMinMaxDimensions({
|
||||
direction,
|
||||
minWidth,
|
||||
maxWidth,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
minWidth: isPinned
|
||||
? parseAndPadSize(minWidth)
|
||||
: parseAndPadSize(minWidth, 36),
|
||||
maxWidth: isPinned
|
||||
? parseAndPadSize(maxWidth)
|
||||
: parseAndPadSize(maxWidth, 36),
|
||||
minHeight: isPinned
|
||||
? parseAndPadSize(minHeight)
|
||||
: parseAndPadSize(minHeight, 36),
|
||||
maxHeight: isPinned
|
||||
? parseAndPadSize(maxHeight)
|
||||
: parseAndPadSize(maxHeight, 36),
|
||||
}),
|
||||
[minWidth, maxWidth, minHeight, maxHeight, direction]
|
||||
[minWidth, maxWidth, minHeight, maxHeight, direction, isPinned]
|
||||
);
|
||||
|
||||
const resizableStyles = useMemo(
|
||||
() => getResizableStyles({ isPinned, direction, sx, handleWidth }),
|
||||
[sx, handleWidth, direction, isPinned]
|
||||
() => getResizableStyles({ isPinned, direction, handleWidth, isResizable }),
|
||||
[handleWidth, direction, isPinned, isResizable]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const { width, height } = getDefaultSize({
|
||||
initialWidth,
|
||||
initialHeight,
|
||||
direction,
|
||||
});
|
||||
|
||||
setWidth(width);
|
||||
setHeight(height);
|
||||
}, [initialWidth, initialHeight, direction, langDirection]);
|
||||
const slideDirection = useMemo(
|
||||
() => getSlideDirection(direction, langDirection),
|
||||
[direction, langDirection]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (['left', 'right'].includes(direction)) {
|
||||
@ -151,7 +153,7 @@ const ResizableDrawer = ({
|
||||
|
||||
return (
|
||||
<Slide
|
||||
direction={direction}
|
||||
direction={slideDirection}
|
||||
in={isOpen}
|
||||
motionProps={{ initial: false }}
|
||||
{...(isPinned
|
||||
@ -165,29 +167,34 @@ const ResizableDrawer = ({
|
||||
},
|
||||
}
|
||||
: {
|
||||
transition: { enter: { duration: 0.2 }, exit: { duration: 0.2 } },
|
||||
style: { zIndex: 98 },
|
||||
transition: { enter: { duration: 0.15 }, exit: { duration: 0.15 } },
|
||||
style: { zIndex: 99, width: 'full' },
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
ref={outsideClickRef}
|
||||
sx={{
|
||||
width: ['left', 'right'].includes(direction) ? 'min-content' : 'full',
|
||||
height: ['left', 'right'].includes(direction)
|
||||
? '100%'
|
||||
: 'min-content',
|
||||
position: 'relative',
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
}}
|
||||
>
|
||||
<ChakraResizeable
|
||||
size={{
|
||||
width: isPinned ? '100%' : width,
|
||||
height: isPinned ? '100%' : height,
|
||||
width,
|
||||
height,
|
||||
}}
|
||||
enable={handleEnables}
|
||||
handleStyles={handleStyles}
|
||||
{...minMaxDimensions}
|
||||
sx={{ ...resizableStyles, height: 'full' }}
|
||||
sx={{
|
||||
borderColor: 'base.700',
|
||||
p: isPinned ? 0 : 4,
|
||||
bg: 'base.900',
|
||||
height: 'full',
|
||||
boxShadow: !isPinned ? '0 0 4rem 0 rgba(0, 0, 0, 0.8)' : '',
|
||||
...resizableStyles,
|
||||
...sx,
|
||||
}}
|
||||
onResizeStart={(event, direction, elementRef) => {
|
||||
onResizeStart && onResizeStart(event, direction, elementRef);
|
||||
}}
|
||||
@ -195,19 +202,16 @@ const ResizableDrawer = ({
|
||||
onResize && onResize(event, direction, elementRef, delta);
|
||||
}}
|
||||
onResizeStop={(event, direction, elementRef, delta) => {
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
if (direction === 'left' || direction === 'right') {
|
||||
if (['left', 'right'].includes(direction)) {
|
||||
setWidth(Number(width) + delta.width);
|
||||
}
|
||||
if (direction === 'top' || direction === 'bottom') {
|
||||
if (['top', 'bottom'].includes(direction)) {
|
||||
setHeight(Number(height) + delta.height);
|
||||
}
|
||||
onResizeStop && onResizeStop(event, direction, elementRef, delta);
|
||||
}}
|
||||
>
|
||||
<Scrollable>{children}</Scrollable>
|
||||
{children}
|
||||
</ChakraResizeable>
|
||||
</Box>
|
||||
</Slide>
|
||||
|
@ -52,10 +52,10 @@ export const getDefaultSize = ({
|
||||
|
||||
export type GetMinMaxDimensionsOptions = {
|
||||
direction: SlideDirection;
|
||||
minWidth?: string | number;
|
||||
maxWidth?: string | number;
|
||||
minHeight?: string | number;
|
||||
maxHeight?: string | number;
|
||||
minWidth?: number;
|
||||
maxWidth?: number;
|
||||
minHeight?: number;
|
||||
maxHeight?: number;
|
||||
};
|
||||
|
||||
// Get the min/max width/height based on direction and provided values
|
||||
@ -78,7 +78,12 @@ export const getMinMaxDimensions = ({
|
||||
const maxH =
|
||||
maxHeight ?? (['top', 'bottom'].includes(direction) ? '95vh' : undefined);
|
||||
|
||||
return { minWidth: minW, maxWidth: maxW, minHeight: minH, maxHeight: maxH };
|
||||
return {
|
||||
...(minW ? { minWidth: minW } : {}),
|
||||
...(maxW ? { maxWidth: maxW } : {}),
|
||||
...(minH ? { minHeight: minH } : {}),
|
||||
...(maxH ? { maxHeight: maxH } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
export type GetHandleStylesOptions = {
|
||||
@ -171,41 +176,76 @@ export const getAnimations = ({
|
||||
};
|
||||
|
||||
export type GetResizableStylesProps = {
|
||||
sx: ChakraProps['sx'];
|
||||
direction: SlideDirection;
|
||||
handleWidth: number;
|
||||
handleWidth: string | number;
|
||||
isPinned: boolean;
|
||||
isResizable: boolean;
|
||||
};
|
||||
|
||||
export const getResizableStyles = ({
|
||||
isPinned, // TODO add borderRadius for pinned?
|
||||
sx,
|
||||
direction,
|
||||
handleWidth,
|
||||
isResizable,
|
||||
}: GetResizableStylesProps): ChakraProps['sx'] => {
|
||||
if (isPinned) {
|
||||
return sx;
|
||||
if (isPinned && !isResizable) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (direction === 'top') {
|
||||
return {
|
||||
borderBottomWidth: handleWidth,
|
||||
...sx,
|
||||
};
|
||||
}
|
||||
|
||||
if (direction === 'right') {
|
||||
return { borderInlineStartWidth: handleWidth, ...sx };
|
||||
return { borderInlineStartWidth: handleWidth };
|
||||
}
|
||||
|
||||
if (direction === 'bottom') {
|
||||
return {
|
||||
borderTopWidth: handleWidth,
|
||||
...sx,
|
||||
};
|
||||
}
|
||||
|
||||
if (direction === 'left') {
|
||||
return { borderInlineEndWidth: handleWidth, ...sx };
|
||||
return { borderInlineEndWidth: handleWidth };
|
||||
}
|
||||
};
|
||||
|
||||
export const getSlideDirection = (
|
||||
direction: SlideDirection,
|
||||
langDirection: LangDirection
|
||||
) => {
|
||||
if (['top', 'bottom'].includes(direction)) {
|
||||
return direction;
|
||||
}
|
||||
|
||||
if (direction === 'left') {
|
||||
if (langDirection === 'rtl') {
|
||||
return 'right';
|
||||
}
|
||||
return 'left';
|
||||
}
|
||||
|
||||
if (direction === 'right') {
|
||||
if (langDirection === 'rtl') {
|
||||
return 'left';
|
||||
}
|
||||
return 'right';
|
||||
}
|
||||
|
||||
return 'left';
|
||||
};
|
||||
|
||||
export const parseAndPadSize = (size?: number, padding?: number) => {
|
||||
if (!size) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!padding) {
|
||||
return size;
|
||||
}
|
||||
|
||||
return size + padding;
|
||||
};
|
||||
|
@ -1,113 +1,84 @@
|
||||
import { Box, ChakraProps } from '@chakra-ui/react';
|
||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||
import { throttle } from 'lodash';
|
||||
import { ReactNode, useEffect, useRef } from 'react';
|
||||
|
||||
const scrollShadowBaseStyles: ChakraProps['sx'] = {
|
||||
position: 'absolute',
|
||||
width: 'full',
|
||||
height: 24,
|
||||
left: 0,
|
||||
pointerEvents: 'none',
|
||||
transition: 'opacity 0.2s',
|
||||
};
|
||||
|
||||
type ScrollableProps = {
|
||||
children: ReactNode;
|
||||
containerProps?: ChakraProps;
|
||||
};
|
||||
|
||||
const Scrollable = ({
|
||||
children,
|
||||
containerProps = {
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
flexShrink: 0,
|
||||
},
|
||||
}: ScrollableProps) => {
|
||||
const Scrollable = ({ children }: ScrollableProps) => {
|
||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||
const topShadowRef = useRef<HTMLDivElement>(null);
|
||||
const bottomShadowRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [initialize, _instance] = useOverlayScrollbars({
|
||||
defer: true,
|
||||
events: {
|
||||
initialized(instance) {
|
||||
if (!topShadowRef.current || !bottomShadowRef.current) {
|
||||
return;
|
||||
}
|
||||
const updateScrollShadowOpacity = throttle(
|
||||
() => {
|
||||
if (
|
||||
!scrollableRef.current ||
|
||||
!topShadowRef.current ||
|
||||
!bottomShadowRef.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { scrollTop, scrollHeight, offsetHeight } = scrollableRef.current;
|
||||
|
||||
const { scrollTop, scrollHeight, offsetHeight } =
|
||||
instance.elements().content;
|
||||
if (scrollTop > 0) {
|
||||
topShadowRef.current.style.opacity = '1';
|
||||
} else {
|
||||
topShadowRef.current.style.opacity = '0';
|
||||
}
|
||||
|
||||
const scrollPercentage = scrollTop / (scrollHeight - offsetHeight);
|
||||
|
||||
topShadowRef.current.style.opacity = String(scrollPercentage * 5);
|
||||
|
||||
bottomShadowRef.current.style.opacity = String(
|
||||
(1 - scrollPercentage) * 5
|
||||
);
|
||||
},
|
||||
scroll: (_instance, event) => {
|
||||
if (
|
||||
!topShadowRef.current ||
|
||||
!bottomShadowRef.current ||
|
||||
!scrollableRef.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { scrollTop, scrollHeight, offsetHeight } =
|
||||
event.target as HTMLDivElement;
|
||||
|
||||
const scrollPercentage = scrollTop / (scrollHeight - offsetHeight);
|
||||
|
||||
topShadowRef.current.style.opacity = String(scrollPercentage * 5);
|
||||
|
||||
bottomShadowRef.current.style.opacity = String(
|
||||
(1 - scrollPercentage) * 5
|
||||
);
|
||||
},
|
||||
if (scrollTop >= scrollHeight - offsetHeight) {
|
||||
bottomShadowRef.current.style.opacity = '0';
|
||||
} else {
|
||||
bottomShadowRef.current.style.opacity = '1';
|
||||
}
|
||||
},
|
||||
});
|
||||
33,
|
||||
{ leading: true }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!scrollableRef.current ||
|
||||
!topShadowRef.current ||
|
||||
!bottomShadowRef.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
topShadowRef.current.style.opacity = '0';
|
||||
|
||||
bottomShadowRef.current.style.opacity = '0';
|
||||
|
||||
initialize(scrollableRef.current);
|
||||
}, [initialize]);
|
||||
updateScrollShadowOpacity();
|
||||
}, [updateScrollShadowOpacity]);
|
||||
|
||||
return (
|
||||
<Box position="relative" w="full" h="full">
|
||||
<Box ref={scrollableRef} {...containerProps} overflowY="scroll">
|
||||
<Box paddingInlineEnd={5}>{children}</Box>
|
||||
<Box
|
||||
ref={scrollableRef}
|
||||
position="absolute"
|
||||
w="full"
|
||||
h="full"
|
||||
overflowY="scroll"
|
||||
onScroll={updateScrollShadowOpacity}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
<Box
|
||||
ref={bottomShadowRef}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
...scrollShadowBaseStyles,
|
||||
bottom: 0,
|
||||
boxShadow:
|
||||
'inset 0 -3.5rem 2rem -2rem var(--invokeai-colors-base-900)',
|
||||
width: 'full',
|
||||
height: 24,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
pointerEvents: 'none',
|
||||
transition: 'opacity 0.2s',
|
||||
}}
|
||||
></Box>
|
||||
<Box
|
||||
ref={topShadowRef}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
...scrollShadowBaseStyles,
|
||||
top: 0,
|
||||
boxShadow:
|
||||
'inset 0 3.5rem 2rem -2rem var(--invokeai-colors-base-900)',
|
||||
width: 'full',
|
||||
height: 24,
|
||||
top: 0,
|
||||
left: 0,
|
||||
pointerEvents: 'none',
|
||||
transition: 'opacity 0.2s',
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
|
@ -16,10 +16,10 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces
|
||||
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
|
||||
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PinParametersPanelButton from 'features/ui/components/common/PinParametersPanelButton';
|
||||
import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings';
|
||||
import { memo } from 'react';
|
||||
|
||||
export default function ImageToImageParameters() {
|
||||
const ImageToImageParameters = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const imageToImageAccordions = {
|
||||
@ -74,7 +74,8 @@ export default function ImageToImageParameters() {
|
||||
<NegativePromptInput />
|
||||
<ProcessButtons />
|
||||
<ParametersAccordion accordionInfo={imageToImageAccordions} />
|
||||
<PinParametersPanelButton />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(ImageToImageParameters);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import InvokeWorkarea from 'features/ui/components/common/InvokeWorkarea';
|
||||
import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
|
||||
import ImageToImageContent from './ImageToImageContent';
|
||||
import ImageToImageParameters from './ImageToImageParameters';
|
||||
|
||||
export default function ImageToImageWorkarea() {
|
||||
return (
|
||||
<InvokeWorkarea parametersPanel={<ImageToImageParameters />}>
|
||||
<InvokeWorkarea parametersPanelContent={<ImageToImageParameters />}>
|
||||
<ImageToImageContent />
|
||||
</InvokeWorkarea>
|
||||
);
|
||||
|
@ -15,10 +15,10 @@ import ParametersAccordion from 'features/parameters/components/ParametersAccord
|
||||
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
|
||||
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
|
||||
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PinParametersPanelButton from 'features/ui/components/common/PinParametersPanelButton';
|
||||
|
||||
export default function TextToImageParameters() {
|
||||
const TextToImageParameters = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const textToImageAccordions = {
|
||||
@ -63,12 +63,13 @@ export default function TextToImageParameters() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={2} position="relative">
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<PromptInput />
|
||||
<NegativePromptInput />
|
||||
<ProcessButtons />
|
||||
<ParametersAccordion accordionInfo={textToImageAccordions} />
|
||||
<PinParametersPanelButton />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(TextToImageParameters);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import InvokeWorkarea from 'features/ui/components/common/InvokeWorkarea';
|
||||
import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
|
||||
import TextToImageContent from './TextToImageContent';
|
||||
import TextToImageParameters from './TextToImageParameters';
|
||||
|
||||
export default function TextToImageWorkarea() {
|
||||
return (
|
||||
<InvokeWorkarea parametersPanel={<TextToImageParameters />}>
|
||||
<InvokeWorkarea parametersPanelContent={<TextToImageParameters />}>
|
||||
<TextToImageContent />
|
||||
</InvokeWorkarea>
|
||||
);
|
||||
|
@ -5,12 +5,12 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAICanvas from 'features/canvas/components/IAICanvas';
|
||||
import IAICanvasResizer from 'features/canvas/components/IAICanvasResizer';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
|
||||
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useLayoutEffect } from 'react';
|
||||
import UnifiedCanvasToolbarBeta from './UnifiedCanvasToolbarBeta';
|
||||
import UnifiedCanvasToolSettingsBeta from './UnifiedCanvasToolSettingsBeta';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
|
||||
const selector = createSelector(
|
||||
[canvasSelector],
|
||||
@ -33,11 +33,10 @@ const UnifiedCanvasContentBeta = () => {
|
||||
const { doesCanvasNeedScaling } = useAppSelector(selector);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
|
||||
const resizeCallback = debounce(() => {
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
}, 250);
|
||||
dispatch(requestCanvasRescale());
|
||||
const resizeCallback = () => {
|
||||
dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
window.addEventListener('resize', resizeCallback);
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { Flex } from '@chakra-ui/layout';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
|
||||
import InvokeButton from 'features/parameters/components/ProcessButtons/InvokeButton';
|
||||
import { setShouldShowParametersPanel } from 'features/ui/store/uiSlice';
|
||||
@ -19,9 +19,7 @@ export default function UnifiedCanvasProcessingButtons() {
|
||||
|
||||
const handleShowOptionsPanel = () => {
|
||||
dispatch(setShouldShowParametersPanel(true));
|
||||
if (shouldPinParametersPanel) {
|
||||
setTimeout(() => dispatch(setDoesCanvasNeedScaling(true)), 400);
|
||||
}
|
||||
shouldPinParametersPanel && dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import IAICanvasRedoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton';
|
||||
import IAICanvasUndoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasUndoButton';
|
||||
import UnifiedCanvasCopyToClipboard from './UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard';
|
||||
@ -18,10 +16,6 @@ import UnifiedCanvasToolSelect from './UnifiedCanvasToolbar/UnifiedCanvasToolSel
|
||||
import UnifiedCanvasSettings from './UnifiedCanvasToolSettings/UnifiedCanvasSettings';
|
||||
|
||||
const UnifiedCanvasToolbarBeta = () => {
|
||||
const shouldShowParametersPanel = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldShowParametersPanel
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" rowGap={2}>
|
||||
<UnifiedCanvasLayerSelect />
|
||||
@ -52,7 +46,7 @@ const UnifiedCanvasToolbarBeta = () => {
|
||||
</Flex>
|
||||
|
||||
<UnifiedCanvasSettings />
|
||||
{!shouldShowParametersPanel && <UnifiedCanvasProcessingButtons />}
|
||||
<UnifiedCanvasProcessingButtons />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -6,8 +6,8 @@ import IAICanvas from 'features/canvas/components/IAICanvas';
|
||||
import IAICanvasResizer from 'features/canvas/components/IAICanvasResizer';
|
||||
import IAICanvasToolbar from 'features/canvas/components/IAICanvasToolbar/IAICanvasToolbar';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
|
||||
import { debounce, isEqual } from 'lodash';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { useLayoutEffect } from 'react';
|
||||
|
||||
@ -32,11 +32,11 @@ const UnifiedCanvasContent = () => {
|
||||
const { doesCanvasNeedScaling } = useAppSelector(selector);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
dispatch(requestCanvasRescale());
|
||||
|
||||
const resizeCallback = debounce(() => {
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
}, 250);
|
||||
const resizeCallback = () => {
|
||||
dispatch(requestCanvasRescale());
|
||||
};
|
||||
|
||||
window.addEventListener('resize', resizeCallback);
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
// import { Feature } from 'app/features';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { Feature } from 'app/features';
|
||||
import BoundingBoxSettings from 'features/parameters/components/AdvancedParameters/Canvas/BoundingBox/BoundingBoxSettings';
|
||||
@ -16,7 +15,6 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces
|
||||
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
|
||||
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PinParametersPanelButton from 'features/ui/components/common/PinParametersPanelButton';
|
||||
|
||||
export default function UnifiedCanvasParameters() {
|
||||
const { t } = useTranslation();
|
||||
@ -71,7 +69,6 @@ export default function UnifiedCanvasParameters() {
|
||||
<NegativePromptInput />
|
||||
<ProcessButtons />
|
||||
<ParametersAccordion accordionInfo={unifiedCanvasAccordions} />
|
||||
<PinParametersPanelButton />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import InvokeWorkarea from 'features/ui/components/common/InvokeWorkarea';
|
||||
import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta';
|
||||
import UnifiedCanvasContent from './UnifiedCanvasContent';
|
||||
import UnifiedCanvasParameters from './UnifiedCanvasParameters';
|
||||
@ -9,13 +10,17 @@ export default function UnifiedCanvasWorkarea() {
|
||||
const shouldUseCanvasBetaLayout = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldUseCanvasBetaLayout
|
||||
);
|
||||
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
return (
|
||||
<InvokeWorkarea parametersPanel={<UnifiedCanvasParameters />}>
|
||||
{shouldUseCanvasBetaLayout ? (
|
||||
<UnifiedCanvasContentBeta />
|
||||
) : (
|
||||
<UnifiedCanvasContent />
|
||||
)}
|
||||
<InvokeWorkarea parametersPanelContent={<UnifiedCanvasParameters />}>
|
||||
{activeTabName === 'unifiedCanvas' &&
|
||||
(shouldUseCanvasBetaLayout ? (
|
||||
<UnifiedCanvasContentBeta />
|
||||
) : (
|
||||
<UnifiedCanvasContent />
|
||||
))}
|
||||
</InvokeWorkarea>
|
||||
);
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ const initialtabsState: UIState = {
|
||||
shouldShowExistingModelsInSearch: false,
|
||||
shouldUseSliders: false,
|
||||
addNewModelUIOption: null,
|
||||
shouldPinGallery: true,
|
||||
shouldShowGallery: true,
|
||||
};
|
||||
|
||||
const initialState: UIState = initialtabsState;
|
||||
@ -63,6 +65,33 @@ export const uiSlice = createSlice({
|
||||
setAddNewModelUIOption: (state, action: PayloadAction<AddNewModelType>) => {
|
||||
state.addNewModelUIOption = action.payload;
|
||||
},
|
||||
setShouldPinGallery: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldPinGallery = action.payload;
|
||||
},
|
||||
setShouldShowGallery: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldShowGallery = action.payload;
|
||||
},
|
||||
togglePinGalleryPanel: (state) => {
|
||||
state.shouldPinGallery = !state.shouldPinGallery;
|
||||
},
|
||||
togglePinParametersPanel: (state) => {
|
||||
state.shouldPinParametersPanel = !state.shouldPinParametersPanel;
|
||||
},
|
||||
toggleParametersPanel: (state) => {
|
||||
state.shouldShowParametersPanel = !state.shouldShowParametersPanel;
|
||||
},
|
||||
toggleGalleryPanel: (state) => {
|
||||
state.shouldShowGallery = !state.shouldShowGallery;
|
||||
},
|
||||
togglePanels: (state) => {
|
||||
if (state.shouldShowGallery || state.shouldShowParametersPanel) {
|
||||
state.shouldShowGallery = false;
|
||||
state.shouldShowParametersPanel = false;
|
||||
} else {
|
||||
state.shouldShowGallery = true;
|
||||
state.shouldShowParametersPanel = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -77,6 +106,13 @@ export const {
|
||||
setShouldShowExistingModelsInSearch,
|
||||
setShouldUseSliders,
|
||||
setAddNewModelUIOption,
|
||||
setShouldPinGallery,
|
||||
setShouldShowGallery,
|
||||
togglePanels,
|
||||
togglePinGalleryPanel,
|
||||
togglePinParametersPanel,
|
||||
toggleParametersPanel,
|
||||
toggleGalleryPanel,
|
||||
} = uiSlice.actions;
|
||||
|
||||
export default uiSlice.reducer;
|
||||
|
@ -11,4 +11,6 @@ export interface UIState {
|
||||
shouldShowExistingModelsInSearch: boolean;
|
||||
shouldUseSliders: boolean;
|
||||
addNewModelUIOption: AddNewModelType;
|
||||
shouldPinGallery: boolean;
|
||||
shouldShowGallery: boolean;
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
.os-scrollbar {
|
||||
/* --os-size: 0; */
|
||||
/* --os-padding-perpendicular: 0; */
|
||||
/* --os-padding-axis: 0; */
|
||||
/* --os-track-border-radius: 0; */
|
||||
/* --os-track-bg: none; */
|
||||
/* --os-track-bg-hover: none; */
|
||||
/* --os-track-bg-active: none; */
|
||||
/* --os-track-border: none; */
|
||||
/* --os-track-border-hover: none; */
|
||||
/* --os-track-border-active: none; */
|
||||
/* --os-handle-border-radius: 0; */
|
||||
--os-handle-bg: var(--invokeai-colors-accent-600);
|
||||
--os-handle-bg-hover: var(--invokeai-colors-accent-550);
|
||||
--os-handle-bg-active: var(--invokeai-colors-accent-500);
|
||||
/* --os-handle-border: none; */
|
||||
/* --os-handle-border-hover: none; */
|
||||
/* --os-handle-border-active: none; */
|
||||
/* --os-handle-min-size: 33px; */
|
||||
/* --os-handle-max-size: none; */
|
||||
/* --os-handle-perpendicular-size: 100%; */
|
||||
/* --os-handle-perpendicular-size-hover: 100%; */
|
||||
/* --os-handle-perpendicular-size-active: 100%; */
|
||||
/* --os-handle-interactive-area-offset: 0; */
|
||||
}
|
@ -12,7 +12,7 @@ import { modalTheme } from './components/modal';
|
||||
import { numberInputTheme } from './components/numberInput';
|
||||
import { popoverTheme } from './components/popover';
|
||||
import { progressTheme } from './components/progress';
|
||||
import { no_scrollbar, scrollbar } from './components/scrollbar';
|
||||
import { no_scrollbar, scrollbar as _scrollbar } from './components/scrollbar';
|
||||
import { selectTheme } from './components/select';
|
||||
import { sliderTheme } from './components/slider';
|
||||
import { switchTheme } from './components/switch';
|
||||
@ -31,7 +31,7 @@ export const theme: ThemeOverride = {
|
||||
color: 'base.50',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
...no_scrollbar,
|
||||
'*': { ...no_scrollbar },
|
||||
}),
|
||||
},
|
||||
direction: 'ltr',
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Calc Values
|
||||
export const APP_CUTOFF = '0px';
|
||||
export const APP_CONTENT_HEIGHT_CUTOFF = 'calc(70px + 1rem)'; // default: 7rem
|
||||
export const APP_PADDING = 4;
|
||||
export const PROGRESS_BAR_THICKNESS = 1.5;
|
||||
export const APP_WIDTH = `calc(100vw - ${APP_CUTOFF})`;
|
||||
export const APP_HEIGHT = `calc(100vh - ${PROGRESS_BAR_THICKNESS * 4}px)`;
|
||||
|
File diff suppressed because one or more lines are too long
@ -4204,16 +4204,6 @@ os-tmpdir@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
|
||||
|
||||
overlayscrollbars-react@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/overlayscrollbars-react/-/overlayscrollbars-react-0.5.0.tgz#0272bdc6304c7228a58d30e5b678e97fd5c5d8dd"
|
||||
integrity sha512-uCNTnkfWW74veoiEv3kSwoLelKt4e8gTNv65D771X3il0x5g5Yo0fUbro7SpQzR9yNgi23cvB2mQHTTdQH96pA==
|
||||
|
||||
overlayscrollbars@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-2.1.0.tgz#d647034ef388980e0e5e092f7429c501215330a1"
|
||||
integrity sha512-L6p4o4aWse5pDstRnJjZaos+al+bkuAgzGIlWwlsxRSgW6+7Kvrp+kAzlWoTZ1bgB4CJj+8u5bjdq8XHEhWjrw==
|
||||
|
||||
p-cancelable@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
|
||||
|
Loading…
Reference in New Issue
Block a user