Add support for custom light color palette (#2156)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2025-01-04 18:53:47 -05:00
committed by GitHub
parent e46706777c
commit e65653c107
10 changed files with 70 additions and 29 deletions

View File

@@ -1,8 +1,9 @@
import { expect } from '@playwright/test' import { expect } from '@playwright/test'
import type { Palette } from '../src/types/colorPaletteTypes'
import { comfyPageFixture as test } from './fixtures/ComfyPage' import { comfyPageFixture as test } from './fixtures/ComfyPage'
const customColorPalettes = { const customColorPalettes: Record<string, Palette> = {
obsidian: { obsidian: {
version: 102, version: 102,
id: 'obsidian', id: 'obsidian',
@@ -128,6 +129,19 @@ const customColorPalettes = {
'tr-odd-bg-color': 'rgba(19,19,19,.9)' 'tr-odd-bg-color': 'rgba(19,19,19,.9)'
} }
} }
},
// A custom light theme with fg color red
light_red: {
id: 'light_red',
name: 'Light Red',
light_theme: true,
colors: {
node_slot: {},
litegraph_base: {},
comfy_base: {
'fg-color': '#ff0000'
}
}
} }
} }
@@ -142,6 +156,12 @@ test.describe('Color Palette', () => {
await expect(comfyPage.canvas).toHaveScreenshot( await expect(comfyPage.canvas).toHaveScreenshot(
'custom-color-palette-obsidian-dark.png' 'custom-color-palette-obsidian-dark.png'
) )
await comfyPage.setSetting('Comfy.ColorPalette', 'light_red')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'custom-color-palette-light-red.png'
)
await comfyPage.setSetting('Comfy.ColorPalette', 'dark') await comfyPage.setSetting('Comfy.ColorPalette', 'dark')
await comfyPage.nextFrame() await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot('default-color-palette.png') await expect(comfyPage.canvas).toHaveScreenshot('default-color-palette.png')

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -1,6 +1,7 @@
{ {
"id": "light", "id": "light",
"name": "Light", "name": "Light",
"light_theme": true,
"colors": { "colors": {
"node_slot": { "node_slot": {
"CLIP": "#FFA726", "CLIP": "#FFA726",
@@ -13,7 +14,12 @@
"MASK": "#9CCC65", "MASK": "#9CCC65",
"MODEL": "#7E57C2", "MODEL": "#7E57C2",
"STYLE_MODEL": "#D4E157", "STYLE_MODEL": "#D4E157",
"VAE": "#FF7043" "VAE": "#FF7043",
"NOISE": "#B0B0B0",
"GUIDER": "#66FFFF",
"SAMPLER": "#ECB4B4",
"SIGMAS": "#CDFFCD",
"TAESD": "#DCC274"
}, },
"litegraph_base": { "litegraph_base": {
"BACKGROUND_IMAGE": "data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==", "BACKGROUND_IMAGE": "data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==",

View File

@@ -11,14 +11,15 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useCommandStore } from '@/stores/commandStore' import { useCommandStore } from '@/stores/commandStore'
import { useSettingStore } from '@/stores/settingStore' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import SidebarIcon from './SidebarIcon.vue' import SidebarIcon from './SidebarIcon.vue'
const settingStore = useSettingStore() const colorPaletteStore = useColorPaletteStore()
const currentTheme = computed(() => settingStore.get('Comfy.ColorPalette'))
const icon = computed(() => const icon = computed(() =>
currentTheme.value !== 'light' ? 'pi pi-moon' : 'pi pi-sun' colorPaletteStore.completedActivePalette.light_theme
? 'pi pi-sun'
: 'pi pi-moon'
) )
const commandStore = useCommandStore() const commandStore = useCommandStore()

View File

@@ -4,7 +4,7 @@ import github from '@/assets/palettes/github.json'
import light from '@/assets/palettes/light.json' import light from '@/assets/palettes/light.json'
import nord from '@/assets/palettes/nord.json' import nord from '@/assets/palettes/nord.json'
import solarized from '@/assets/palettes/solarized.json' import solarized from '@/assets/palettes/solarized.json'
import type { ColorPalettes } from '@/types/colorPaletteTypes' import type { ColorPalettes, CompletedPalette } from '@/types/colorPaletteTypes'
export const CORE_COLOR_PALETTES: ColorPalettes = { export const CORE_COLOR_PALETTES: ColorPalettes = {
dark, dark,
@@ -15,4 +15,6 @@ export const CORE_COLOR_PALETTES: ColorPalettes = {
github github
} as const } as const
export const DEFAULT_COLOR_PALETTE = dark export const DEFAULT_COLOR_PALETTE: CompletedPalette = dark
export const DEFAULT_DARK_COLOR_PALETTE: CompletedPalette = dark
export const DEFAULT_LIGHT_COLOR_PALETTE: CompletedPalette = light

View File

@@ -5,6 +5,10 @@ import {
LiteGraph LiteGraph
} from '@comfyorg/litegraph' } from '@comfyorg/litegraph'
import {
DEFAULT_DARK_COLOR_PALETTE,
DEFAULT_LIGHT_COLOR_PALETTE
} from '@/constants/coreColorPalettes'
import { api } from '@/scripts/api' import { api } from '@/scripts/api'
import { app } from '@/scripts/app' import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService' import { useDialogService } from '@/services/dialogService'
@@ -16,6 +20,7 @@ import { useSettingStore } from '@/stores/settingStore'
import { useToastStore } from '@/stores/toastStore' import { useToastStore } from '@/stores/toastStore'
import { type ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore' import { type ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore' import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
import { useWorkspaceStore } from '@/stores/workspaceStore' import { useWorkspaceStore } from '@/stores/workspaceStore'
@@ -23,6 +28,7 @@ export function useCoreCommands(): ComfyCommand[] {
const workflowService = useWorkflowService() const workflowService = useWorkflowService()
const workflowStore = useWorkflowStore() const workflowStore = useWorkflowStore()
const dialogService = useDialogService() const dialogService = useDialogService()
const colorPaletteStore = useColorPaletteStore()
const getTracker = () => workflowStore.activeWorkflow?.changeTracker const getTracker = () => workflowStore.activeWorkflow?.changeTracker
const getSelectedNodes = (): LGraphNode[] => { const getSelectedNodes = (): LGraphNode[] => {
@@ -410,18 +416,18 @@ export function useCoreCommands(): ComfyCommand[] {
label: 'Toggle Theme (Dark/Light)', label: 'Toggle Theme (Dark/Light)',
versionAdded: '1.3.12', versionAdded: '1.3.12',
function: (() => { function: (() => {
let previousDarkTheme: string = 'dark' let previousDarkTheme: string = DEFAULT_DARK_COLOR_PALETTE.id
let previousLightTheme: string = DEFAULT_LIGHT_COLOR_PALETTE.id
// Official light theme is the only light theme supported now.
const isDarkMode = (themeId: string) => themeId !== 'light'
return () => { return () => {
const settingStore = useSettingStore() const settingStore = useSettingStore()
const currentTheme = settingStore.get('Comfy.ColorPalette') const theme = colorPaletteStore.completedActivePalette
if (isDarkMode(currentTheme)) { if (theme.light_theme) {
previousDarkTheme = currentTheme previousLightTheme = theme.id
settingStore.set('Comfy.ColorPalette', 'light')
} else {
settingStore.set('Comfy.ColorPalette', previousDarkTheme) settingStore.set('Comfy.ColorPalette', previousDarkTheme)
} else {
previousDarkTheme = theme.id
settingStore.set('Comfy.ColorPalette', previousLightTheme)
} }
} }
})() })()

View File

@@ -3,7 +3,9 @@ import { computed, ref } from 'vue'
import { import {
CORE_COLOR_PALETTES, CORE_COLOR_PALETTES,
DEFAULT_COLOR_PALETTE DEFAULT_COLOR_PALETTE,
DEFAULT_DARK_COLOR_PALETTE,
DEFAULT_LIGHT_COLOR_PALETTE
} from '@/constants/coreColorPalettes' } from '@/constants/coreColorPalettes'
import type { import type {
ColorPalettes, ColorPalettes,
@@ -63,20 +65,24 @@ export const useColorPaletteStore = defineStore('colorPalette', () => {
palette.colors.comfy_base['comfy-menu-bg'] palette.colors.comfy_base['comfy-menu-bg']
} }
const defaultPalette = palette.light_theme
? DEFAULT_LIGHT_COLOR_PALETTE
: DEFAULT_DARK_COLOR_PALETTE
return { return {
...palette, ...palette,
colors: { colors: {
...palette.colors, ...palette.colors,
node_slot: { node_slot: {
...DEFAULT_COLOR_PALETTE.colors.node_slot, ...defaultPalette.colors.node_slot,
...palette.colors.node_slot ...palette.colors.node_slot
}, },
litegraph_base: { litegraph_base: {
...DEFAULT_COLOR_PALETTE.colors.litegraph_base, ...defaultPalette.colors.litegraph_base,
...palette.colors.litegraph_base ...palette.colors.litegraph_base
}, },
comfy_base: { comfy_base: {
...DEFAULT_COLOR_PALETTE.colors.comfy_base, ...defaultPalette.colors.comfy_base,
...palette.colors.comfy_base ...palette.colors.comfy_base
} }
} }

View File

@@ -92,7 +92,8 @@ export const paletteSchema = z
.object({ .object({
id: z.string(), id: z.string(),
name: z.string(), name: z.string(),
colors: partialColorsSchema colors: partialColorsSchema,
light_theme: z.boolean().optional()
}) })
.passthrough() .passthrough()

View File

@@ -13,7 +13,7 @@
import { useEventListener } from '@vueuse/core' import { useEventListener } from '@vueuse/core'
import type { ToastMessageOptions } from 'primevue/toast' import type { ToastMessageOptions } from 'primevue/toast'
import { useToast } from 'primevue/usetoast' import { useToast } from 'primevue/usetoast'
import { computed, onBeforeUnmount, onMounted, watch, watchEffect } from 'vue' import { onBeforeUnmount, onMounted, watch, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import BrowserTabTitle from '@/components/BrowserTabTitle.vue' import BrowserTabTitle from '@/components/BrowserTabTitle.vue'
@@ -41,6 +41,7 @@ import {
import { useServerConfigStore } from '@/stores/serverConfigStore' import { useServerConfigStore } from '@/stores/serverConfigStore'
import { useSettingStore } from '@/stores/settingStore' import { useSettingStore } from '@/stores/settingStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
import { useWorkspaceStore } from '@/stores/workspaceStore' import { useWorkspaceStore } from '@/stores/workspaceStore'
import { StatusWsMessageStatus } from '@/types/apiTypes' import { StatusWsMessageStatus } from '@/types/apiTypes'
@@ -51,18 +52,16 @@ const { t } = useI18n()
const toast = useToast() const toast = useToast()
const settingStore = useSettingStore() const settingStore = useSettingStore()
const executionStore = useExecutionStore() const executionStore = useExecutionStore()
const colorPaletteStore = useColorPaletteStore()
const theme = computed<string>(() => settingStore.get('Comfy.ColorPalette'))
watch( watch(
theme, () => colorPaletteStore.completedActivePalette,
(newTheme) => { (newTheme) => {
const DARK_THEME_CLASS = 'dark-theme' const DARK_THEME_CLASS = 'dark-theme'
const isDarkTheme = newTheme !== 'light' if (newTheme.light_theme) {
if (isDarkTheme) {
document.body.classList.add(DARK_THEME_CLASS)
} else {
document.body.classList.remove(DARK_THEME_CLASS) document.body.classList.remove(DARK_THEME_CLASS)
} else {
document.body.classList.add(DARK_THEME_CLASS)
} }
}, },
{ immediate: true } { immediate: true }