mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 14:30:07 +00:00
Implement color palette in Vue (#2047)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 97 KiB |
@@ -128,7 +128,7 @@ describe('TreeExplorerTreeNode', () => {
|
||||
expect(handleRenameMock).toHaveBeenCalledOnce()
|
||||
expect(addToastSpy).toHaveBeenCalledWith({
|
||||
severity: 'error',
|
||||
summary: 'g.error',
|
||||
summary: 'Error',
|
||||
detail: 'Rename failed',
|
||||
life: 3000
|
||||
})
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<template #header>
|
||||
<CurrentUserMessage v-if="tabValue === 'Comfy'" />
|
||||
<FirstTimeUIMessage v-if="tabValue === 'Comfy'" />
|
||||
<ColorPaletteMessage v-if="tabValue === 'Appearance'" />
|
||||
</template>
|
||||
<SettingsPanel :settingGroups="sortedGroups(category)" />
|
||||
</PanelTemplate>
|
||||
@@ -77,6 +78,7 @@ import PanelTemplate from './setting/PanelTemplate.vue'
|
||||
import AboutPanel from './setting/AboutPanel.vue'
|
||||
import FirstTimeUIMessage from './setting/FirstTimeUIMessage.vue'
|
||||
import CurrentUserMessage from './setting/CurrentUserMessage.vue'
|
||||
import ColorPaletteMessage from './setting/ColorPaletteMessage.vue'
|
||||
import { flattenTree } from '@/utils/treeUtil'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<Message severity="info" icon="pi pi-palette" pt:text="w-full">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
{{ $t('settingsCategories.ColorPalette') }}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<Select
|
||||
class="w-44"
|
||||
v-model="activePaletteId"
|
||||
:options="palettes"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-download"
|
||||
text
|
||||
:title="$t('g.download')"
|
||||
@click="colorPaletteService.exportColorPalette(activePaletteId)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-upload"
|
||||
text
|
||||
:title="$t('g.import')"
|
||||
@click="importCustomPalette"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
text
|
||||
:title="$t('g.delete')"
|
||||
@click="colorPaletteService.deleteCustomColorPalette(activePaletteId)"
|
||||
:disabled="!colorPaletteStore.isCustomPalette(activePaletteId)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Message>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import Message from 'primevue/message'
|
||||
import Select from 'primevue/select'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useColorPaletteService } from '@/services/colorPaletteService'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { watch } from 'vue'
|
||||
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const colorPaletteService = useColorPaletteService()
|
||||
const { palettes, activePaletteId } = storeToRefs(colorPaletteStore)
|
||||
|
||||
const importCustomPalette = async () => {
|
||||
const palette = await colorPaletteService.importColorPalette()
|
||||
if (palette) {
|
||||
colorPaletteService.loadColorPalette(palette.id)
|
||||
}
|
||||
}
|
||||
|
||||
watch(activePaletteId, () => {
|
||||
colorPaletteService.loadColorPalette(activePaletteId.value)
|
||||
})
|
||||
</script>
|
||||
@@ -65,6 +65,8 @@ import { ChangeTracker } from '@/scripts/changeTracker'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { workflowService } from '@/services/workflowService'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useColorPaletteService } from '@/services/colorPaletteService'
|
||||
|
||||
const emit = defineEmits(['ready'])
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
@@ -209,6 +211,13 @@ watchEffect(() => {
|
||||
canvasStore.canvas.canvas.style.cursor = 'default'
|
||||
})
|
||||
|
||||
const colorPaletteService = useColorPaletteService()
|
||||
watchEffect(() => {
|
||||
if (!canvasStore.canvas) return
|
||||
|
||||
colorPaletteService.loadColorPalette(settingStore.get('Comfy.ColorPalette'))
|
||||
})
|
||||
|
||||
const workflowStore = useWorkflowStore()
|
||||
const persistCurrentWorkflow = () => {
|
||||
const workflow = JSON.stringify(comfyApp.serializeGraph())
|
||||
@@ -315,6 +324,12 @@ onMounted(async () => {
|
||||
|
||||
comfyAppReady.value = true
|
||||
|
||||
// Load color palette
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
colorPaletteStore.customPalettes = settingStore.get(
|
||||
'Comfy.CustomColorPalettes'
|
||||
)
|
||||
|
||||
// Start watching for locale change after the initial value is loaded.
|
||||
watch(
|
||||
() => settingStore.get('Comfy.Locale'),
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import {
|
||||
defaultColorPalette,
|
||||
getColorPalette
|
||||
} from '@/extensions/core/colorPalette'
|
||||
import { app } from '@/scripts/app'
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import { BadgePosition } from '@comfyorg/litegraph'
|
||||
@@ -18,9 +14,11 @@ import { LGraphBadge } from '@comfyorg/litegraph'
|
||||
import _ from 'lodash'
|
||||
import { NodeBadgeMode } from '@/types/nodeSource'
|
||||
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import type { Palette } from '@/types/colorPaletteTypes'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
|
||||
const nodeSourceBadgeMode = computed(
|
||||
() => settingStore.get('Comfy.NodeBadge.NodeSourceBadgeMode') as NodeBadgeMode
|
||||
)
|
||||
@@ -36,10 +34,6 @@ watch([nodeSourceBadgeMode, nodeIdBadgeMode, nodeLifeCycleBadgeMode], () => {
|
||||
app.graph?.setDirtyCanvas(true, true)
|
||||
})
|
||||
|
||||
const colorPalette = computed<Palette | undefined>(() =>
|
||||
getColorPalette(settingStore.get('Comfy.ColorPalette'))
|
||||
)
|
||||
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
function badgeTextVisible(
|
||||
nodeDef: ComfyNodeDefImpl | null,
|
||||
@@ -79,11 +73,11 @@ onMounted(() => {
|
||||
}
|
||||
),
|
||||
fgColor:
|
||||
colorPalette.value?.colors?.litegraph_base?.BADGE_FG_COLOR ||
|
||||
defaultColorPalette.colors.litegraph_base.BADGE_FG_COLOR,
|
||||
colorPaletteStore.completedActivePalette.colors.litegraph_base
|
||||
.BADGE_FG_COLOR,
|
||||
bgColor:
|
||||
colorPalette.value?.colors?.litegraph_base?.BADGE_BG_COLOR ||
|
||||
defaultColorPalette.colors.litegraph_base.BADGE_BG_COLOR
|
||||
colorPaletteStore.completedActivePalette.colors.litegraph_base
|
||||
.BADGE_BG_COLOR
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -81,12 +81,10 @@ https://github.com/Nuked88/ComfyUI-N-Sidebar/blob/7ae7da4a9761009fb6629bc04c6830
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
||||
import {
|
||||
getColorPalette,
|
||||
defaultColorPalette
|
||||
} from '@/extensions/core/colorPalette'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import _ from 'lodash'
|
||||
import { useWidgetStore } from '@/stores/widgetStore'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
nodeDef: {
|
||||
@@ -95,12 +93,10 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
// Node preview currently is recreated every time something is hovered.
|
||||
// So not reactive to the color palette changes after setup is fine.
|
||||
// If later we want NodePreview to be shown more persistently, then we should
|
||||
// make the getColorPalette() call reactive.
|
||||
const colors = getColorPalette()?.colors?.litegraph_base
|
||||
const litegraphColors = colors ?? defaultColorPalette.colors.litegraph_base
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const litegraphColors = computed(
|
||||
() => colorPaletteStore.completedActivePalette.colors.litegraph_base
|
||||
)
|
||||
|
||||
const widgetStore = useWidgetStore()
|
||||
|
||||
|
||||
@@ -14,3 +14,5 @@ export const CORE_COLOR_PALETTES: ColorPalettes = {
|
||||
nord,
|
||||
github
|
||||
} as const
|
||||
|
||||
export const DEFAULT_COLOR_PALETTE = dark
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ColorPalettes } from '@/types/colorPaletteTypes'
|
||||
import type { Keybinding } from '@/types/keyBindingTypes'
|
||||
import { NodeBadgeMode } from '@/types/nodeSource'
|
||||
import { LinkReleaseTriggerAction } from '@/types/searchBoxTypes'
|
||||
@@ -663,5 +664,19 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
versionAdded: '1.5.6'
|
||||
},
|
||||
{
|
||||
id: 'Comfy.ColorPalette',
|
||||
name: 'The active color palette id',
|
||||
type: 'hidden',
|
||||
defaultValue: 'dark',
|
||||
versionModified: '1.6.7'
|
||||
},
|
||||
{
|
||||
id: 'Comfy.CustomColorPalettes',
|
||||
name: 'Custom color palettes',
|
||||
type: 'hidden',
|
||||
defaultValue: {} as ColorPalettes,
|
||||
versionModified: '1.6.7'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,483 +0,0 @@
|
||||
// @ts-strict-ignore
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { app } from '../../scripts/app'
|
||||
import { $el } from '../../scripts/ui'
|
||||
import type { ColorPalettes, Palette } from '@/types/colorPaletteTypes'
|
||||
import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph'
|
||||
import { CORE_COLOR_PALETTES } from '@/constants/coreColorPalettes'
|
||||
// Manage color palettes
|
||||
|
||||
const colorPalettes = CORE_COLOR_PALETTES
|
||||
const id = 'Comfy.ColorPalette'
|
||||
const idCustomColorPalettes = 'Comfy.CustomColorPalettes'
|
||||
const defaultColorPaletteId = 'dark'
|
||||
const els: { select: HTMLSelectElement | null } = {
|
||||
select: null
|
||||
}
|
||||
|
||||
const getCustomColorPalettes = (): ColorPalettes => {
|
||||
return app.ui.settings.getSettingValue(idCustomColorPalettes, {})
|
||||
}
|
||||
|
||||
const setCustomColorPalettes = (customColorPalettes: ColorPalettes) => {
|
||||
return app.ui.settings.setSettingValue(
|
||||
idCustomColorPalettes,
|
||||
customColorPalettes
|
||||
)
|
||||
}
|
||||
|
||||
export const defaultColorPalette = colorPalettes[defaultColorPaletteId]
|
||||
export const getColorPalette = (
|
||||
colorPaletteId?: string
|
||||
): Palette | undefined => {
|
||||
if (!colorPaletteId) {
|
||||
colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId)
|
||||
}
|
||||
|
||||
if (colorPaletteId.startsWith('custom_')) {
|
||||
colorPaletteId = colorPaletteId.substr(7)
|
||||
let customColorPalettes = getCustomColorPalettes()
|
||||
if (customColorPalettes[colorPaletteId]) {
|
||||
return customColorPalettes[colorPaletteId]
|
||||
}
|
||||
}
|
||||
|
||||
return colorPalettes[colorPaletteId]
|
||||
}
|
||||
|
||||
const setColorPalette = (colorPaletteId) => {
|
||||
app.ui.settings.setSettingValue(id, colorPaletteId)
|
||||
}
|
||||
|
||||
// const ctxMenu = LiteGraph.ContextMenu;
|
||||
app.registerExtension({
|
||||
name: id,
|
||||
init() {
|
||||
/**
|
||||
* Changes the background color of the canvas.
|
||||
*
|
||||
* @method updateBackground
|
||||
* @param {image} String
|
||||
* @param {clearBackgroundColor} String
|
||||
*/
|
||||
// @ts-expect-error
|
||||
LGraphCanvas.prototype.updateBackground = function (
|
||||
image,
|
||||
clearBackgroundColor
|
||||
) {
|
||||
this._bg_img = new Image()
|
||||
this._bg_img.name = image
|
||||
this._bg_img.src = image
|
||||
this._bg_img.onload = () => {
|
||||
this.draw(true, true)
|
||||
}
|
||||
this.background_image = image
|
||||
|
||||
this.clear_background = true
|
||||
this.clear_background_color = clearBackgroundColor
|
||||
this._pattern = null
|
||||
}
|
||||
},
|
||||
addCustomNodeDefs(node_defs) {
|
||||
const sortObjectKeys = (unordered) => {
|
||||
return Object.keys(unordered)
|
||||
.sort()
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = unordered[key]
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
|
||||
function getSlotTypes() {
|
||||
var types = []
|
||||
|
||||
const defs = node_defs
|
||||
for (const nodeId in defs) {
|
||||
const nodeData = defs[nodeId]
|
||||
|
||||
var inputs = nodeData['input']['required']
|
||||
if (nodeData['input']['optional'] !== undefined) {
|
||||
inputs = Object.assign(
|
||||
{},
|
||||
nodeData['input']['required'],
|
||||
nodeData['input']['optional']
|
||||
)
|
||||
}
|
||||
|
||||
for (const inputName in inputs) {
|
||||
const inputData = inputs[inputName]
|
||||
const type = inputData[0]
|
||||
|
||||
if (!Array.isArray(type)) {
|
||||
types.push(type)
|
||||
}
|
||||
}
|
||||
|
||||
for (const o in nodeData['output']) {
|
||||
const output = nodeData['output'][o]
|
||||
types.push(output)
|
||||
}
|
||||
}
|
||||
|
||||
return types
|
||||
}
|
||||
|
||||
function completeColorPalette(colorPalette) {
|
||||
var types = getSlotTypes()
|
||||
|
||||
for (const type of types) {
|
||||
if (!colorPalette.colors.node_slot[type]) {
|
||||
colorPalette.colors.node_slot[type] = ''
|
||||
}
|
||||
}
|
||||
|
||||
colorPalette.colors.node_slot = sortObjectKeys(
|
||||
colorPalette.colors.node_slot
|
||||
)
|
||||
|
||||
return colorPalette
|
||||
}
|
||||
|
||||
const getColorPaletteTemplate = async () => {
|
||||
const colorPalette: Palette = {
|
||||
id: 'my_color_palette_unique_id',
|
||||
name: 'My Color Palette',
|
||||
colors: {
|
||||
node_slot: {},
|
||||
litegraph_base: {},
|
||||
comfy_base: {}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy over missing keys from default color palette
|
||||
const defaultColorPalette = colorPalettes[defaultColorPaletteId]
|
||||
for (const key in defaultColorPalette.colors.litegraph_base) {
|
||||
colorPalette.colors.litegraph_base[key] ||= ''
|
||||
}
|
||||
for (const key in defaultColorPalette.colors.comfy_base) {
|
||||
colorPalette.colors.comfy_base[key] ||= ''
|
||||
}
|
||||
|
||||
return completeColorPalette(colorPalette)
|
||||
}
|
||||
|
||||
const addCustomColorPalette = async (colorPalette) => {
|
||||
if (typeof colorPalette !== 'object') {
|
||||
useToastStore().addAlert('Invalid color palette.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!colorPalette.id) {
|
||||
useToastStore().addAlert('Color palette missing id.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!colorPalette.name) {
|
||||
useToastStore().addAlert('Color palette missing name.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!colorPalette.colors) {
|
||||
useToastStore().addAlert('Color palette missing colors.')
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
colorPalette.colors.node_slot &&
|
||||
typeof colorPalette.colors.node_slot !== 'object'
|
||||
) {
|
||||
useToastStore().addAlert('Invalid color palette colors.node_slot.')
|
||||
return
|
||||
}
|
||||
|
||||
const customColorPalettes = getCustomColorPalettes()
|
||||
customColorPalettes[colorPalette.id] = colorPalette
|
||||
setCustomColorPalettes(customColorPalettes)
|
||||
|
||||
for (const option of els.select.childNodes) {
|
||||
if (
|
||||
(option as HTMLOptionElement).value ===
|
||||
'custom_' + colorPalette.id
|
||||
) {
|
||||
els.select.removeChild(option)
|
||||
}
|
||||
}
|
||||
|
||||
els.select.append(
|
||||
$el('option', {
|
||||
textContent: colorPalette.name + ' (custom)',
|
||||
value: 'custom_' + colorPalette.id,
|
||||
selected: true
|
||||
})
|
||||
)
|
||||
|
||||
setColorPalette('custom_' + colorPalette.id)
|
||||
await loadColorPalette(colorPalette)
|
||||
}
|
||||
|
||||
const deleteCustomColorPalette = async (colorPaletteId) => {
|
||||
const customColorPalettes = getCustomColorPalettes()
|
||||
delete customColorPalettes[colorPaletteId]
|
||||
setCustomColorPalettes(customColorPalettes)
|
||||
|
||||
for (const opt of els.select.childNodes) {
|
||||
const option = opt as HTMLOptionElement
|
||||
if (option.value === defaultColorPaletteId) {
|
||||
option.selected = true
|
||||
}
|
||||
|
||||
if (option.value === 'custom_' + colorPaletteId) {
|
||||
els.select.removeChild(option)
|
||||
}
|
||||
}
|
||||
|
||||
setColorPalette(defaultColorPaletteId)
|
||||
await loadColorPalette(getColorPalette())
|
||||
}
|
||||
|
||||
const loadColorPalette = async (colorPalette: Palette) => {
|
||||
colorPalette = await completeColorPalette(colorPalette)
|
||||
if (colorPalette.colors) {
|
||||
// Sets the colors of node slots and links
|
||||
if (colorPalette.colors.node_slot) {
|
||||
Object.assign(
|
||||
app.canvas.default_connection_color_byType,
|
||||
colorPalette.colors.node_slot
|
||||
)
|
||||
Object.assign(
|
||||
LGraphCanvas.link_type_colors,
|
||||
colorPalette.colors.node_slot
|
||||
)
|
||||
}
|
||||
// Sets the colors of the LiteGraph objects
|
||||
if (colorPalette.colors.litegraph_base) {
|
||||
// Everything updates correctly in the loop, except the Node Title and Link Color for some reason
|
||||
app.canvas.node_title_color =
|
||||
colorPalette.colors.litegraph_base.NODE_TITLE_COLOR
|
||||
app.canvas.default_link_color =
|
||||
colorPalette.colors.litegraph_base.LINK_COLOR
|
||||
|
||||
for (const key in colorPalette.colors.litegraph_base) {
|
||||
if (
|
||||
colorPalette.colors.litegraph_base.hasOwnProperty(key) &&
|
||||
LiteGraph.hasOwnProperty(key)
|
||||
) {
|
||||
const value = colorPalette.colors.litegraph_base[key]
|
||||
if (key === 'NODE_DEFAULT_SHAPE' && typeof value === 'string') {
|
||||
console.warn(
|
||||
`litegraph_base.NODE_DEFAULT_SHAPE only accepts [${[
|
||||
LiteGraph.BOX_SHAPE,
|
||||
LiteGraph.ROUND_SHAPE,
|
||||
LiteGraph.CARD_SHAPE
|
||||
].join(', ')}] but got ${value}`
|
||||
)
|
||||
LiteGraph.NODE_DEFAULT_SHAPE = LiteGraph.ROUND_SHAPE
|
||||
} else {
|
||||
LiteGraph[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sets the color of ComfyUI elements
|
||||
if (colorPalette.colors.comfy_base) {
|
||||
const rootStyle = document.documentElement.style
|
||||
for (const key in colorPalette.colors.comfy_base) {
|
||||
rootStyle.setProperty(
|
||||
'--' + key,
|
||||
colorPalette.colors.comfy_base[key]
|
||||
)
|
||||
}
|
||||
}
|
||||
// Sets special case colors
|
||||
if (colorPalette.colors.litegraph_base.NODE_BYPASS_BGCOLOR) {
|
||||
app.bypassBgColor =
|
||||
colorPalette.colors.litegraph_base.NODE_BYPASS_BGCOLOR
|
||||
}
|
||||
app.canvas.setDirty(true, true)
|
||||
}
|
||||
}
|
||||
|
||||
const fileInput = $el('input', {
|
||||
type: 'file',
|
||||
accept: '.json',
|
||||
style: { display: 'none' },
|
||||
parent: document.body,
|
||||
onchange: () => {
|
||||
const file = fileInput.files[0]
|
||||
if (file.type === 'application/json' || file.name.endsWith('.json')) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = async () => {
|
||||
await addCustomColorPalette(JSON.parse(reader.result as string))
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
}
|
||||
}) as HTMLInputElement
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
id,
|
||||
category: ['Appearance', 'ColorPalette'],
|
||||
name: 'Color Palette',
|
||||
type: (name, setter, value) => {
|
||||
const options = [
|
||||
...Object.values(colorPalettes).map((c) =>
|
||||
$el('option', {
|
||||
textContent: c.name,
|
||||
value: c.id,
|
||||
selected: c.id === value
|
||||
})
|
||||
),
|
||||
...Object.values(getCustomColorPalettes()).map((c) =>
|
||||
$el('option', {
|
||||
textContent: `${c.name} (custom)`,
|
||||
value: `custom_${c.id}`,
|
||||
selected: `custom_${c.id}` === value
|
||||
})
|
||||
)
|
||||
]
|
||||
|
||||
els.select = $el(
|
||||
'select',
|
||||
{
|
||||
style: {
|
||||
marginBottom: '0.15rem',
|
||||
width: '100%'
|
||||
},
|
||||
onchange: (e) => {
|
||||
setter(e.target.value)
|
||||
}
|
||||
},
|
||||
options
|
||||
) as HTMLSelectElement
|
||||
|
||||
return $el('tr', [
|
||||
$el('td', [
|
||||
els.select,
|
||||
$el(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'grid',
|
||||
gap: '4px',
|
||||
gridAutoFlow: 'column'
|
||||
}
|
||||
},
|
||||
[
|
||||
$el('input', {
|
||||
type: 'button',
|
||||
value: 'Export',
|
||||
onclick: async () => {
|
||||
const colorPaletteId = app.ui.settings.getSettingValue(
|
||||
id,
|
||||
defaultColorPaletteId
|
||||
)
|
||||
const colorPalette = await completeColorPalette(
|
||||
getColorPalette(colorPaletteId)
|
||||
)
|
||||
const json = JSON.stringify(colorPalette, null, 2) // convert the data to a JSON string
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = $el('a', {
|
||||
href: url,
|
||||
download: colorPaletteId + '.json',
|
||||
style: { display: 'none' },
|
||||
parent: document.body
|
||||
})
|
||||
a.click()
|
||||
setTimeout(function () {
|
||||
a.remove()
|
||||
window.URL.revokeObjectURL(url)
|
||||
}, 0)
|
||||
}
|
||||
}),
|
||||
$el('input', {
|
||||
type: 'button',
|
||||
value: 'Import',
|
||||
onclick: () => {
|
||||
fileInput.click()
|
||||
}
|
||||
}),
|
||||
$el('input', {
|
||||
type: 'button',
|
||||
value: 'Template',
|
||||
onclick: async () => {
|
||||
const colorPalette = await getColorPaletteTemplate()
|
||||
const json = JSON.stringify(colorPalette, null, 2) // convert the data to a JSON string
|
||||
const blob = new Blob([json], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = $el('a', {
|
||||
href: url,
|
||||
download: 'color_palette.json',
|
||||
style: { display: 'none' },
|
||||
parent: document.body
|
||||
})
|
||||
a.click()
|
||||
setTimeout(function () {
|
||||
a.remove()
|
||||
window.URL.revokeObjectURL(url)
|
||||
}, 0)
|
||||
}
|
||||
}),
|
||||
$el('input', {
|
||||
type: 'button',
|
||||
value: 'Delete',
|
||||
onclick: async () => {
|
||||
let colorPaletteId = app.ui.settings.getSettingValue(
|
||||
id,
|
||||
defaultColorPaletteId
|
||||
)
|
||||
|
||||
if (colorPalettes[colorPaletteId]) {
|
||||
useToastStore().addAlert(
|
||||
'You cannot delete a built-in color palette.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (colorPaletteId.startsWith('custom_')) {
|
||||
colorPaletteId = colorPaletteId.substr(7)
|
||||
}
|
||||
|
||||
await deleteCustomColorPalette(colorPaletteId)
|
||||
}
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
])
|
||||
},
|
||||
defaultValue: defaultColorPaletteId,
|
||||
async onChange(value) {
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
|
||||
let palette = colorPalettes[value]
|
||||
if (palette) {
|
||||
await loadColorPalette(palette)
|
||||
} else if (value.startsWith('custom_')) {
|
||||
value = value.substr(7)
|
||||
let customColorPalettes = getCustomColorPalettes()
|
||||
if (customColorPalettes[value]) {
|
||||
palette = customColorPalettes[value]
|
||||
await loadColorPalette(customColorPalettes[value])
|
||||
}
|
||||
}
|
||||
|
||||
let { BACKGROUND_IMAGE, CLEAR_BACKGROUND_COLOR } =
|
||||
palette.colors.litegraph_base
|
||||
if (
|
||||
BACKGROUND_IMAGE === undefined ||
|
||||
CLEAR_BACKGROUND_COLOR === undefined
|
||||
) {
|
||||
const base = colorPalettes['dark'].colors.litegraph_base
|
||||
BACKGROUND_IMAGE = base.BACKGROUND_IMAGE
|
||||
CLEAR_BACKGROUND_COLOR = base.CLEAR_BACKGROUND_COLOR
|
||||
}
|
||||
// @ts-expect-error
|
||||
// litegraph.extensions.js
|
||||
app.canvas.updateBackground(BACKGROUND_IMAGE, CLEAR_BACKGROUND_COLOR)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,4 @@
|
||||
import './clipspace'
|
||||
import './colorPalette'
|
||||
import './contextMenuFilter'
|
||||
import './dynamicPrompts'
|
||||
import './editAttention'
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { t } from '@/i18n'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export function useErrorHandling() {
|
||||
const toast = useToastStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const toastErrorHandler = (error: any) => {
|
||||
toast.add({
|
||||
@@ -15,12 +14,12 @@ export function useErrorHandling() {
|
||||
}
|
||||
|
||||
const wrapWithErrorHandling =
|
||||
(
|
||||
action: (...args: any[]) => any,
|
||||
<TArgs extends any[], TReturn>(
|
||||
action: (...args: TArgs) => TReturn,
|
||||
errorHandler?: (error: any) => void,
|
||||
finallyHandler?: () => void
|
||||
) =>
|
||||
(...args: any[]) => {
|
||||
(...args: TArgs): TReturn | undefined => {
|
||||
try {
|
||||
return action(...args)
|
||||
} catch (e) {
|
||||
@@ -31,12 +30,12 @@ export function useErrorHandling() {
|
||||
}
|
||||
|
||||
const wrapWithErrorHandlingAsync =
|
||||
(
|
||||
action: ((...args: any[]) => Promise<any>) | ((...args: any[]) => any),
|
||||
<TArgs extends any[], TReturn>(
|
||||
action: (...args: TArgs) => Promise<TReturn> | TReturn,
|
||||
errorHandler?: (error: any) => void,
|
||||
finallyHandler?: () => void
|
||||
) =>
|
||||
async (...args: any[]) => {
|
||||
async (...args: TArgs): Promise<TReturn | undefined> => {
|
||||
try {
|
||||
return await action(...args)
|
||||
} catch (e) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"comingSoon": "Coming Soon",
|
||||
"firstTimeUIMessage": "This is the first time you use the new UI. Choose \"Menu > Use New Menu > Disabled\" to restore the old UI.",
|
||||
"download": "Download",
|
||||
"import": "Import",
|
||||
"loadAllFolders": "Load All Folders",
|
||||
"refresh": "Refresh",
|
||||
"terminal": "Terminal",
|
||||
@@ -427,7 +428,8 @@
|
||||
"Window": "Window",
|
||||
"Server-Config": "Server-Config",
|
||||
"About": "About",
|
||||
"EditTokenWeight": "Edit Token Weight"
|
||||
"EditTokenWeight": "Edit Token Weight",
|
||||
"CustomColorPalettes": "Custom Color Palettes"
|
||||
},
|
||||
"serverConfigItems": {
|
||||
"listen": {
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Send anonymous crash reports"
|
||||
},
|
||||
"Comfy_ColorPalette": {
|
||||
"name": "Color Palette"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "Require confirmation when clearing workflow"
|
||||
},
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"goToNode": "ノードに移動",
|
||||
"icon": "アイコン",
|
||||
"imageFailedToLoad": "画像の読み込みに失敗しました",
|
||||
"import": "インポート",
|
||||
"insert": "挿入",
|
||||
"install": "インストール",
|
||||
"keybinding": "キーバインディング",
|
||||
@@ -541,6 +542,7 @@
|
||||
"ColorPalette": "カラーパレット",
|
||||
"Comfy": "Comfy",
|
||||
"Comfy-Desktop": "Comfyデスクトップ",
|
||||
"CustomColorPalettes": "カスタムカラーパレット",
|
||||
"DevMode": "開発モード",
|
||||
"EditTokenWeight": "トークンの重みを編集",
|
||||
"Extension": "拡張",
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "匿名のクラッシュレポートを送信する"
|
||||
},
|
||||
"Comfy_ColorPalette": {
|
||||
"name": "カラーパレット"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "ワークフローをクリアする際に確認を要求する"
|
||||
},
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"goToNode": "노드로 이동",
|
||||
"icon": "아이콘",
|
||||
"imageFailedToLoad": "이미지를 로드하지 못했습니다.",
|
||||
"import": "가져오기",
|
||||
"insert": "삽입",
|
||||
"install": "설치",
|
||||
"keybinding": "키 바인딩",
|
||||
@@ -541,6 +542,7 @@
|
||||
"ColorPalette": "색상 팔레트",
|
||||
"Comfy": "Comfy",
|
||||
"Comfy-Desktop": "Comfy-Desktop",
|
||||
"CustomColorPalettes": "사용자 정의 색상 팔레트",
|
||||
"DevMode": "개발자 모드",
|
||||
"EditTokenWeight": "토큰 가중치 편집",
|
||||
"Extension": "확장",
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "익명으로 충돌 보고서 전송"
|
||||
},
|
||||
"Comfy_ColorPalette": {
|
||||
"name": "색상 팔레트"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "워크플로 비우기 시 확인 요구"
|
||||
},
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"goToNode": "Перейти к узлу",
|
||||
"icon": "Иконка",
|
||||
"imageFailedToLoad": "Не удалось загрузить изображение",
|
||||
"import": "Импорт",
|
||||
"insert": "Вставить",
|
||||
"install": "Установить",
|
||||
"keybinding": "Привязка клавиш",
|
||||
@@ -541,6 +542,7 @@
|
||||
"ColorPalette": "Цветовая палитра",
|
||||
"Comfy": "Comfy",
|
||||
"Comfy-Desktop": "Comfy рабочий стол",
|
||||
"CustomColorPalettes": "Пользовательские цветовые палитры",
|
||||
"DevMode": "Режим разработчика",
|
||||
"EditTokenWeight": "Редактировать вес токена",
|
||||
"Extension": "Расширение",
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Отправлять анонимные отчеты о сбоях"
|
||||
},
|
||||
"Comfy_ColorPalette": {
|
||||
"name": "Цветовая палитра"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "Требовать подтверждение при очистке рабочего процесса"
|
||||
},
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"goToNode": "转到节点",
|
||||
"icon": "图标",
|
||||
"imageFailedToLoad": "图像加载失败",
|
||||
"import": "导入",
|
||||
"insert": "插入",
|
||||
"install": "安装",
|
||||
"keybinding": "快捷键",
|
||||
@@ -541,6 +542,7 @@
|
||||
"ColorPalette": "调色板",
|
||||
"Comfy": "Comfy",
|
||||
"Comfy-Desktop": "Comfy桌面版",
|
||||
"CustomColorPalettes": "自定义颜色调色板",
|
||||
"DevMode": "开发模式",
|
||||
"EditTokenWeight": "编辑令牌权重",
|
||||
"Extension": "扩展",
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "发送匿名崩溃报告"
|
||||
},
|
||||
"Comfy_ColorPalette": {
|
||||
"name": "调色板"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "清除工作流时需要确认"
|
||||
},
|
||||
|
||||
@@ -110,11 +110,7 @@ export async function addStylesheet(
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { string } filename
|
||||
* @param { Blob } blob
|
||||
*/
|
||||
export function downloadBlob(filename, blob) {
|
||||
export function downloadBlob(filename: string, blob: Blob) {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = $el('a', {
|
||||
href: url,
|
||||
@@ -129,6 +125,20 @@ export function downloadBlob(filename, blob) {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export function uploadFile(accept: string) {
|
||||
return new Promise<File>((resolve, reject) => {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = accept
|
||||
input.onchange = (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0]
|
||||
if (!file) return reject(new Error('No file selected'))
|
||||
resolve(file)
|
||||
}
|
||||
input.click()
|
||||
})
|
||||
}
|
||||
|
||||
export function prop<T>(
|
||||
target: object,
|
||||
name: string,
|
||||
|
||||
186
src/services/colorPaletteService.ts
Normal file
186
src/services/colorPaletteService.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useErrorHandling } from '@/hooks/errorHooks'
|
||||
import { Colors, paletteSchema, type Palette } from '@/types/colorPaletteTypes'
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
import { LGraphCanvas } from '@comfyorg/litegraph'
|
||||
import { LiteGraph } from '@comfyorg/litegraph'
|
||||
import { app } from '@/scripts/app'
|
||||
import { downloadBlob, uploadFile } from '@/scripts/utils'
|
||||
import { toRaw } from 'vue'
|
||||
|
||||
export const useColorPaletteService = () => {
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const settingStore = useSettingStore()
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const { wrapWithErrorHandling, wrapWithErrorHandlingAsync } =
|
||||
useErrorHandling()
|
||||
|
||||
/**
|
||||
* Validates the palette against the zod schema.
|
||||
*
|
||||
* @param data - The palette to validate.
|
||||
* @returns The validated palette.
|
||||
*/
|
||||
const validateColorPalette = (data: unknown): Palette => {
|
||||
const result = paletteSchema.safeParse(data)
|
||||
if (result.success) return result.data
|
||||
|
||||
const error = fromZodError(result.error)
|
||||
throw new Error(`Invalid color palette against zod schema:\n${error}`)
|
||||
}
|
||||
|
||||
const persistCustomColorPalettes = () => {
|
||||
settingStore.set(
|
||||
'Comfy.CustomColorPalettes',
|
||||
colorPaletteStore.customPalettes
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a custom color palette.
|
||||
*
|
||||
* @param colorPaletteId - The ID of the color palette to delete.
|
||||
*/
|
||||
const deleteCustomColorPalette = (colorPaletteId: string) => {
|
||||
colorPaletteStore.deleteCustomPalette(colorPaletteId)
|
||||
persistCustomColorPalettes()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom color palette.
|
||||
*
|
||||
* @param colorPalette - The palette to add.
|
||||
*/
|
||||
const addCustomColorPalette = (colorPalette: Palette) => {
|
||||
validateColorPalette(colorPalette)
|
||||
colorPaletteStore.addCustomPalette(colorPalette)
|
||||
persistCustomColorPalettes()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the colors of node slots and links.
|
||||
*
|
||||
* @param linkColorPalette - The palette to set.
|
||||
*/
|
||||
const loadLinkColorPalette = (linkColorPalette: Colors['node_slot']) => {
|
||||
const types = Object.fromEntries(
|
||||
Array.from(nodeDefStore.nodeDataTypes).map((type) => [type, ''])
|
||||
)
|
||||
Object.assign(
|
||||
app.canvas.default_connection_color_byType,
|
||||
types,
|
||||
linkColorPalette
|
||||
)
|
||||
Object.assign(LGraphCanvas.link_type_colors, types, linkColorPalette)
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the LiteGraph color palette.
|
||||
*
|
||||
* @param liteGraphColorPalette - The palette to set.
|
||||
*/
|
||||
const loadLiteGraphColorPalette = (palette: Colors['litegraph_base']) => {
|
||||
// Sets special case colors
|
||||
app.bypassBgColor = palette.NODE_BYPASS_BGCOLOR
|
||||
|
||||
// Sets the colors of the LiteGraph objects
|
||||
app.canvas.node_title_color = palette.NODE_TITLE_COLOR
|
||||
app.canvas.default_link_color = palette.LINK_COLOR
|
||||
app.canvas.background_image = palette.BACKGROUND_IMAGE
|
||||
app.canvas.clear_background_color = palette.CLEAR_BACKGROUND_COLOR
|
||||
app.canvas._pattern = undefined
|
||||
|
||||
for (const [key, value] of Object.entries(palette)) {
|
||||
if (Object.prototype.hasOwnProperty.call(LiteGraph, key)) {
|
||||
if (key === 'NODE_DEFAULT_SHAPE' && typeof value === 'string') {
|
||||
console.warn(
|
||||
`litegraph_base.NODE_DEFAULT_SHAPE only accepts [${[
|
||||
LiteGraph.BOX_SHAPE,
|
||||
LiteGraph.ROUND_SHAPE,
|
||||
LiteGraph.CARD_SHAPE
|
||||
].join(', ')}] but got ${value}`
|
||||
)
|
||||
LiteGraph.NODE_DEFAULT_SHAPE = LiteGraph.ROUND_SHAPE
|
||||
} else {
|
||||
;(LiteGraph as any)[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the Comfy color palette.
|
||||
*
|
||||
* @param comfyColorPalette - The palette to set.
|
||||
*/
|
||||
const loadComfyColorPalette = (comfyColorPalette: Colors['comfy_base']) => {
|
||||
if (comfyColorPalette) {
|
||||
const rootStyle = document.documentElement.style
|
||||
for (const [key, value] of Object.entries(comfyColorPalette)) {
|
||||
rootStyle.setProperty('--' + key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the color palette.
|
||||
*
|
||||
* @param colorPaletteId - The ID of the color palette to load.
|
||||
*/
|
||||
const loadColorPalette = async (colorPaletteId: string) => {
|
||||
const colorPalette = colorPaletteStore.palettesLookup[colorPaletteId]
|
||||
if (!colorPalette) {
|
||||
throw new Error(`Color palette ${colorPaletteId} not found`)
|
||||
}
|
||||
|
||||
const completedPalette = colorPaletteStore.completePalette(colorPalette)
|
||||
loadLinkColorPalette(completedPalette.colors.node_slot)
|
||||
loadLiteGraphColorPalette(completedPalette.colors.litegraph_base)
|
||||
loadComfyColorPalette(completedPalette.colors.comfy_base)
|
||||
app.canvas.setDirty(true, true)
|
||||
|
||||
colorPaletteStore.activePaletteId = colorPaletteId
|
||||
settingStore.set('Comfy.ColorPalette', colorPaletteId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a color palette.
|
||||
*
|
||||
* @param colorPaletteId - The ID of the color palette to export.
|
||||
*/
|
||||
const exportColorPalette = (colorPaletteId: string) => {
|
||||
const colorPalette = colorPaletteStore.palettesLookup[colorPaletteId]
|
||||
if (!colorPalette) {
|
||||
throw new Error(`Color palette ${colorPaletteId} not found`)
|
||||
}
|
||||
downloadBlob(
|
||||
colorPalette.id + '.json',
|
||||
new Blob([JSON.stringify(toRaw(colorPalette), null, 2)], {
|
||||
type: 'application/json'
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a color palette.
|
||||
*
|
||||
* @returns The imported palette.
|
||||
*/
|
||||
const importColorPalette = async () => {
|
||||
const file = await uploadFile('application/json')
|
||||
const text = await file.text()
|
||||
const palette = JSON.parse(text)
|
||||
addCustomColorPalette(palette)
|
||||
return palette
|
||||
}
|
||||
|
||||
return {
|
||||
addCustomColorPalette: wrapWithErrorHandling(addCustomColorPalette),
|
||||
deleteCustomColorPalette: wrapWithErrorHandling(deleteCustomColorPalette),
|
||||
loadColorPalette: wrapWithErrorHandling(loadColorPalette),
|
||||
exportColorPalette: wrapWithErrorHandling(exportColorPalette),
|
||||
importColorPalette: wrapWithErrorHandlingAsync(importColorPalette)
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,8 @@ import { useWidgetStore } from './widgetStore'
|
||||
|
||||
/**
|
||||
* These extensions are always active, even if they are disabled in the setting.
|
||||
* TODO(https://github.com/Comfy-Org/ComfyUI_frontend/issues/1996):
|
||||
* Migrate logic to out of extensions/core, as features provided
|
||||
* by these extensions are now essential to core.
|
||||
*/
|
||||
export const ALWAYS_ENABLED_EXTENSIONS: readonly string[] = [
|
||||
'Comfy.ColorPalette'
|
||||
]
|
||||
export const ALWAYS_ENABLED_EXTENSIONS: readonly string[] = []
|
||||
|
||||
export const ALWAYS_DISABLED_EXTENSIONS: readonly string[] = [
|
||||
// pysssss.Locking is replaced by pin/unpin in ComfyUI core.
|
||||
|
||||
@@ -319,6 +319,18 @@ export const useNodeDefStore = defineStore('nodeDef', () => {
|
||||
const showExperimental = ref(false)
|
||||
|
||||
const nodeDefs = computed(() => Object.values(nodeDefsByName.value))
|
||||
const nodeDataTypes = computed(() => {
|
||||
const types = new Set<string>()
|
||||
for (const nodeDef of nodeDefs.value) {
|
||||
for (const input of nodeDef.inputs.all) {
|
||||
types.add(input.type)
|
||||
}
|
||||
for (const output of nodeDef.outputs.all) {
|
||||
types.add(output.type)
|
||||
}
|
||||
}
|
||||
return types
|
||||
})
|
||||
const visibleNodeDefs = computed(() =>
|
||||
nodeDefs.value.filter(
|
||||
(nodeDef: ComfyNodeDefImpl) =>
|
||||
@@ -365,6 +377,7 @@ export const useNodeDefStore = defineStore('nodeDef', () => {
|
||||
showExperimental,
|
||||
|
||||
nodeDefs,
|
||||
nodeDataTypes,
|
||||
visibleNodeDefs,
|
||||
nodeSearchService,
|
||||
nodeTree,
|
||||
|
||||
92
src/stores/workspace/colorPaletteStore.ts
Normal file
92
src/stores/workspace/colorPaletteStore.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type {
|
||||
ColorPalettes,
|
||||
CompletedPalette,
|
||||
Palette
|
||||
} from '@/types/colorPaletteTypes'
|
||||
import {
|
||||
CORE_COLOR_PALETTES,
|
||||
DEFAULT_COLOR_PALETTE
|
||||
} from '@/constants/coreColorPalettes'
|
||||
|
||||
export const useColorPaletteStore = defineStore('colorPalette', () => {
|
||||
const customPalettes = ref<ColorPalettes>({})
|
||||
const activePaletteId = ref<string>(DEFAULT_COLOR_PALETTE.id)
|
||||
|
||||
const palettesLookup = computed(() => ({
|
||||
...CORE_COLOR_PALETTES,
|
||||
...customPalettes.value
|
||||
}))
|
||||
|
||||
const palettes = computed(() => Object.values(palettesLookup.value))
|
||||
const completedActivePalette = computed(() =>
|
||||
completePalette(palettesLookup.value[activePaletteId.value])
|
||||
)
|
||||
|
||||
const addCustomPalette = (palette: Palette) => {
|
||||
if (palette.id in palettesLookup.value) {
|
||||
throw new Error(`Palette with id ${palette.id} already exists`)
|
||||
}
|
||||
|
||||
customPalettes.value[palette.id] = palette
|
||||
activePaletteId.value = palette.id
|
||||
}
|
||||
|
||||
const deleteCustomPalette = (id: string) => {
|
||||
if (!(id in customPalettes.value)) {
|
||||
throw new Error(`Palette with id ${id} does not exist`)
|
||||
}
|
||||
|
||||
delete customPalettes.value[id]
|
||||
activePaletteId.value = CORE_COLOR_PALETTES.dark.id
|
||||
}
|
||||
|
||||
const isCustomPalette = (id: string) => {
|
||||
return id in customPalettes.value
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes the palette with default values for missing colors.
|
||||
*
|
||||
* @param palette - The palette to complete.
|
||||
* @returns The completed palette.
|
||||
*/
|
||||
const completePalette = (palette: Palette): CompletedPalette => {
|
||||
return {
|
||||
...palette,
|
||||
colors: {
|
||||
...palette.colors,
|
||||
node_slot: {
|
||||
...DEFAULT_COLOR_PALETTE.colors.node_slot,
|
||||
...palette.colors.node_slot
|
||||
},
|
||||
litegraph_base: {
|
||||
...DEFAULT_COLOR_PALETTE.colors.litegraph_base,
|
||||
...palette.colors.litegraph_base
|
||||
},
|
||||
comfy_base: {
|
||||
...DEFAULT_COLOR_PALETTE.colors.comfy_base,
|
||||
...palette.colors.comfy_base
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
customPalettes,
|
||||
activePaletteId,
|
||||
|
||||
// Getters
|
||||
palettesLookup,
|
||||
palettes,
|
||||
completedActivePalette,
|
||||
|
||||
// Actions
|
||||
isCustomPalette,
|
||||
addCustomPalette,
|
||||
deleteCustomPalette,
|
||||
completePalette
|
||||
}
|
||||
})
|
||||
@@ -2,65 +2,54 @@ import { LiteGraph } from '@comfyorg/litegraph'
|
||||
import { z } from 'zod'
|
||||
|
||||
const nodeSlotSchema = z.object({
|
||||
BOOLEAN: z.string().optional(),
|
||||
CLIP: z.string().optional(),
|
||||
CLIP_VISION: z.string().optional(),
|
||||
CLIP_VISION_OUTPUT: z.string().optional(),
|
||||
CONDITIONING: z.string().optional(),
|
||||
CONTROL_NET: z.string().optional(),
|
||||
CONTROL_NET_WEIGHTS: z.string().optional(),
|
||||
FLOAT: z.string().optional(),
|
||||
GLIGEN: z.string().optional(),
|
||||
IMAGE: z.string().optional(),
|
||||
IMAGEUPLOAD: z.string().optional(),
|
||||
INT: z.string().optional(),
|
||||
LATENT: z.string().optional(),
|
||||
LATENT_KEYFRAME: z.string().optional(),
|
||||
MASK: z.string().optional(),
|
||||
MODEL: z.string().optional(),
|
||||
SAMPLER: z.string().optional(),
|
||||
SIGMAS: z.string().optional(),
|
||||
STRING: z.string().optional(),
|
||||
STYLE_MODEL: z.string().optional(),
|
||||
T2I_ADAPTER_WEIGHTS: z.string().optional(),
|
||||
TAESD: z.string().optional(),
|
||||
TIMESTEP_KEYFRAME: z.string().optional(),
|
||||
UPSCALE_MODEL: z.string().optional(),
|
||||
VAE: z.string().optional()
|
||||
CLIP: z.string(),
|
||||
CLIP_VISION: z.string(),
|
||||
CLIP_VISION_OUTPUT: z.string(),
|
||||
CONDITIONING: z.string(),
|
||||
CONTROL_NET: z.string(),
|
||||
IMAGE: z.string(),
|
||||
LATENT: z.string(),
|
||||
MASK: z.string(),
|
||||
MODEL: z.string(),
|
||||
STYLE_MODEL: z.string(),
|
||||
VAE: z.string(),
|
||||
NOISE: z.string(),
|
||||
GUIDER: z.string(),
|
||||
SAMPLER: z.string(),
|
||||
SIGMAS: z.string(),
|
||||
TAESD: z.string()
|
||||
})
|
||||
|
||||
const litegraphBaseSchema = z.object({
|
||||
BACKGROUND_IMAGE: z.string().optional(),
|
||||
CLEAR_BACKGROUND_COLOR: z.string().optional(),
|
||||
NODE_TITLE_COLOR: z.string().optional(),
|
||||
NODE_SELECTED_TITLE_COLOR: z.string().optional(),
|
||||
NODE_TEXT_SIZE: z.number().optional(),
|
||||
NODE_TEXT_COLOR: z.string().optional(),
|
||||
NODE_SUBTEXT_SIZE: z.number().optional(),
|
||||
NODE_DEFAULT_COLOR: z.string().optional(),
|
||||
NODE_DEFAULT_BGCOLOR: z.string().optional(),
|
||||
NODE_DEFAULT_BOXCOLOR: z.string().optional(),
|
||||
NODE_DEFAULT_SHAPE: z
|
||||
.union([
|
||||
z.literal(LiteGraph.BOX_SHAPE),
|
||||
z.literal(LiteGraph.ROUND_SHAPE),
|
||||
z.literal(LiteGraph.CARD_SHAPE)
|
||||
])
|
||||
.optional(),
|
||||
NODE_BOX_OUTLINE_COLOR: z.string().optional(),
|
||||
NODE_BYPASS_BGCOLOR: z.string().optional(),
|
||||
NODE_ERROR_COLOUR: z.string().optional(),
|
||||
DEFAULT_SHADOW_COLOR: z.string().optional(),
|
||||
DEFAULT_GROUP_FONT: z.number().optional(),
|
||||
WIDGET_BGCOLOR: z.string().optional(),
|
||||
WIDGET_OUTLINE_COLOR: z.string().optional(),
|
||||
WIDGET_TEXT_COLOR: z.string().optional(),
|
||||
WIDGET_SECONDARY_TEXT_COLOR: z.string().optional(),
|
||||
LINK_COLOR: z.string().optional(),
|
||||
EVENT_LINK_COLOR: z.string().optional(),
|
||||
CONNECTING_LINK_COLOR: z.string().optional(),
|
||||
BADGE_FG_COLOR: z.string().optional(),
|
||||
BADGE_BG_COLOR: z.string().optional()
|
||||
BACKGROUND_IMAGE: z.string(),
|
||||
CLEAR_BACKGROUND_COLOR: z.string(),
|
||||
NODE_TITLE_COLOR: z.string(),
|
||||
NODE_SELECTED_TITLE_COLOR: z.string(),
|
||||
NODE_TEXT_SIZE: z.number(),
|
||||
NODE_TEXT_COLOR: z.string(),
|
||||
NODE_SUBTEXT_SIZE: z.number(),
|
||||
NODE_DEFAULT_COLOR: z.string(),
|
||||
NODE_DEFAULT_BGCOLOR: z.string(),
|
||||
NODE_DEFAULT_BOXCOLOR: z.string(),
|
||||
NODE_DEFAULT_SHAPE: z.union([
|
||||
z.literal(LiteGraph.BOX_SHAPE),
|
||||
z.literal(LiteGraph.ROUND_SHAPE),
|
||||
z.literal(LiteGraph.CARD_SHAPE)
|
||||
]),
|
||||
NODE_BOX_OUTLINE_COLOR: z.string(),
|
||||
NODE_BYPASS_BGCOLOR: z.string(),
|
||||
NODE_ERROR_COLOUR: z.string(),
|
||||
DEFAULT_SHADOW_COLOR: z.string(),
|
||||
DEFAULT_GROUP_FONT: z.number(),
|
||||
WIDGET_BGCOLOR: z.string(),
|
||||
WIDGET_OUTLINE_COLOR: z.string(),
|
||||
WIDGET_TEXT_COLOR: z.string(),
|
||||
WIDGET_SECONDARY_TEXT_COLOR: z.string(),
|
||||
LINK_COLOR: z.string(),
|
||||
EVENT_LINK_COLOR: z.string(),
|
||||
CONNECTING_LINK_COLOR: z.string(),
|
||||
BADGE_FG_COLOR: z.string(),
|
||||
BADGE_BG_COLOR: z.string()
|
||||
})
|
||||
|
||||
const comfyBaseSchema = z.object({
|
||||
@@ -68,7 +57,7 @@ const comfyBaseSchema = z.object({
|
||||
['bg-color']: z.string(),
|
||||
['bg-img']: z.string().optional(),
|
||||
['comfy-menu-bg']: z.string(),
|
||||
['comfy-menu-secondary-bg']: z.string().optional(),
|
||||
['comfy-menu-secondary-bg']: z.string(),
|
||||
['comfy-input-bg']: z.string(),
|
||||
['input-text']: z.string(),
|
||||
['descrip-text']: z.string(),
|
||||
@@ -84,15 +73,25 @@ const comfyBaseSchema = z.object({
|
||||
['bar-shadow']: z.string()
|
||||
})
|
||||
|
||||
const colorsSchema = z
|
||||
.object({
|
||||
node_slot: nodeSlotSchema,
|
||||
litegraph_base: litegraphBaseSchema,
|
||||
comfy_base: comfyBaseSchema
|
||||
})
|
||||
.passthrough()
|
||||
const colorsSchema = z.object({
|
||||
node_slot: nodeSlotSchema,
|
||||
litegraph_base: litegraphBaseSchema,
|
||||
comfy_base: comfyBaseSchema
|
||||
})
|
||||
|
||||
const paletteSchema = z.object({
|
||||
const partialColorsSchema = z.object({
|
||||
node_slot: nodeSlotSchema.partial(),
|
||||
litegraph_base: litegraphBaseSchema.partial(),
|
||||
comfy_base: comfyBaseSchema.partial()
|
||||
})
|
||||
|
||||
export const paletteSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
colors: partialColorsSchema
|
||||
})
|
||||
|
||||
export const completedPaletteSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
colors: colorsSchema
|
||||
@@ -102,4 +101,5 @@ export const colorPalettesSchema = z.record(paletteSchema)
|
||||
|
||||
export type Colors = z.infer<typeof colorsSchema>
|
||||
export type Palette = z.infer<typeof paletteSchema>
|
||||
export type CompletedPalette = z.infer<typeof completedPaletteSchema>
|
||||
export type ColorPalettes = z.infer<typeof colorPalettesSchema>
|
||||
|
||||
Reference in New Issue
Block a user