import _ from 'lodash' import { defineStore } from 'pinia' import { gte, rcompare, valid } from 'semver' import { ref } from 'vue' import type { Settings } from '@/schemas/apiSchema' import { api } from '@/scripts/api' import { app } from '@/scripts/app' import type { SettingParams } from '@/types/settingTypes' import type { TreeNode } from '@/types/treeExplorerTypes' import { coerceVersion } from '@/utils/versionUtil' export const getSettingInfo = (setting: SettingParams) => { const parts = setting.category || setting.id.split('.') return { category: parts[0] ?? 'Other', subCategory: parts[1] ?? 'Other' } } export interface SettingTreeNode extends TreeNode { data?: SettingParams } function tryMigrateDeprecatedValue( setting: SettingParams | undefined, value: unknown ) { return setting?.migrateDeprecatedValue?.(value) ?? value } function onChange( setting: SettingParams | undefined, newValue: unknown, oldValue: unknown ) { if (setting?.onChange) { setting.onChange(newValue, oldValue) } // Backward compatibility with old settings dialog. // Some extensions still listens event emitted by the old settings dialog. // @ts-expect-error 'setting' is possibly 'undefined'.ts(18048) app.ui.settings.dispatchChange(setting.id, newValue, oldValue) } export const useSettingStore = defineStore('setting', () => { const settingValues = ref>({}) const settingsById = ref>({}) /** * Check if a setting's value exists, i.e. if the user has set it manually. * @param key - The key of the setting to check. * @returns Whether the setting exists. */ function exists(key: string) { return settingValues.value[key] !== undefined } /** * Set a setting value. * @param key - The key of the setting to set. * @param value - The value to set. */ async function set(key: K, value: Settings[K]) { // Clone the incoming value to prevent external mutations const clonedValue = _.cloneDeep(value) const newValue = tryMigrateDeprecatedValue( settingsById.value[key], clonedValue ) const oldValue = get(key) if (newValue === oldValue) return onChange(settingsById.value[key], newValue, oldValue) settingValues.value[key] = newValue await api.storeSetting(key, newValue) } /** * Get a setting value. * @param key - The key of the setting to get. * @returns The value of the setting. */ function get(key: K): Settings[K] { // Clone the value when returning to prevent external mutations return _.cloneDeep(settingValues.value[key] ?? getDefaultValue(key)) } /** * Gets the setting params, asserting the type that is intentionally left off * of {@link settingsById}. * @param key The key of the setting to get. * @returns The setting. */ function getSettingById( key: K ): SettingParams | undefined { return settingsById.value[key] as SettingParams | undefined } /** * Get the default value of a setting. * @param key - The key of the setting to get. * @returns The default value of the setting. */ function getDefaultValue( key: K ): Settings[K] | undefined { // Assertion: settingsById is not typed. const param = getSettingById(key) if (param === undefined) return const versionedDefault = getVersionedDefaultValue(key, param) if (versionedDefault) { return versionedDefault } return typeof param.defaultValue === 'function' ? param.defaultValue() : param.defaultValue } function getVersionedDefaultValue< K extends keyof Settings, TValue = Settings[K] >(key: K, param: SettingParams | undefined): TValue | null { // get default versioned value, skipping if the key is 'Comfy.InstalledVersion' to prevent infinite loop const defaultsByInstallVersion = param?.defaultsByInstallVersion if (defaultsByInstallVersion && key !== 'Comfy.InstalledVersion') { const installedVersion = get('Comfy.InstalledVersion') if (installedVersion) { const sortedVersions = Object.keys(defaultsByInstallVersion).sort( (a, b) => { const versionA = coerceVersion(a, '') const versionB = coerceVersion(b, '') if (!versionA || !versionB) return 0 return rcompare(versionA, versionB) } ) for (const version of sortedVersions) { if (!valid(version)) { continue } const installedSemver = coerceVersion(installedVersion, '') const targetSemver = coerceVersion(version, '') if ( installedSemver && targetSemver && gte(installedSemver, targetSemver) ) { const versionedDefault = defaultsByInstallVersion[ version as `${number}.${number}.${number}` ] return typeof versionedDefault === 'function' ? versionedDefault() : versionedDefault } } } } return null } /** * Register a setting. * @param setting - The setting to register. */ function addSetting(setting: SettingParams) { if (!setting.id) { throw new Error('Settings must have an ID') } if (setting.id in settingsById.value) { throw new Error(`Setting ${setting.id} must have a unique ID.`) } settingsById.value[setting.id] = setting if (settingValues.value[setting.id] !== undefined) { settingValues.value[setting.id] = tryMigrateDeprecatedValue( setting, settingValues.value[setting.id] ) } onChange(setting, get(setting.id), undefined) } /* * Load setting values from server. * This needs to be called before any setting is registered. */ async function loadSettingValues() { if (Object.keys(settingsById.value).length) { throw new Error( 'Setting values must be loaded before any setting is registered.' ) } settingValues.value = await api.getSettings() } return { settingValues, settingsById, addSetting, loadSettingValues, set, get, exists, getDefaultValue } })