diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.stories.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.stories.ts new file mode 100644 index 0000000000..6e15ece0dc --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.stories.ts @@ -0,0 +1,172 @@ +import type { + ComponentPropsAndSlots, + Meta, + StoryObj +} from '@storybook/vue3-vite' +import { computed, ref, toRefs } from 'vue' + +import type { SimplifiedWidget } from '@/types/simplifiedWidget' + +import WidgetInputText from './WidgetInputText.vue' + +interface StoryArgs extends ComponentPropsAndSlots { + readOnly: boolean + disabled: boolean + invalid: boolean + placeholder: string +} + +const meta: Meta = { + title: 'Widgets/WidgetInputText', + component: WidgetInputText, + tags: ['autodocs'], + parameters: { layout: 'centered' }, + argTypes: { + size: { + control: 'select', + options: ['medium', 'large'] + }, + readOnly: { control: 'boolean' }, + disabled: { control: 'boolean' }, + invalid: { control: 'boolean' }, + loading: { control: 'boolean' }, + placeholder: { control: 'text' } + }, + args: { + size: 'medium', + readOnly: false, + disabled: false, + invalid: false, + loading: false, + placeholder: '' + }, + decorators: [ + (story) => ({ + components: { story }, + template: + '
' + }) + ] +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: (args) => ({ + components: { WidgetInputText }, + setup() { + const { size, readOnly, disabled, invalid, loading, placeholder } = + toRefs(args) + const value = ref('Hello world') + const widget = computed>(() => ({ + name: 'text', + type: 'STRING', + value: '', + options: { + read_only: readOnly.value, + disabled: disabled.value, + ...(placeholder.value ? { placeholder: placeholder.value } : {}) + } + })) + return { value, widget, size, invalid, loading } + }, + template: + '' + }) +} + +export const Disabled: Story = { + args: { disabled: true }, + render: (args) => ({ + components: { WidgetInputText }, + setup() { + const { disabled } = toRefs(args) + const value = ref('This text is disabled') + const widget = computed>(() => ({ + name: 'locked', + type: 'STRING', + value: '', + options: { disabled: disabled.value } + })) + return { value, widget } + }, + template: '' + }) +} + +export const Invalid: Story = { + args: { invalid: true }, + render: (args) => ({ + components: { WidgetInputText }, + setup() { + const { invalid } = toRefs(args) + const value = ref('Invalid input value') + const widget: SimplifiedWidget = { + name: 'text', + type: 'STRING', + value: '' + } + return { value, widget, invalid } + }, + template: + '' + }) +} + +export const Status: Story = { + args: { loading: true }, + render: (args) => ({ + components: { WidgetInputText }, + setup() { + const { loading } = toRefs(args) + const value = ref('Loading...') + const widget: SimplifiedWidget = { + name: 'text', + type: 'STRING', + value: '' + } + return { value, widget, loading } + }, + template: + '' + }) +} + +export const WithPlaceholder: Story = { + args: { placeholder: 'Enter your prompt here...' }, + render: (args) => ({ + components: { WidgetInputText }, + setup() { + const { placeholder } = toRefs(args) + const value = ref('') + const widget = computed>(() => ({ + name: 'prompt', + type: 'STRING', + value: '', + options: { + ...(placeholder.value ? { placeholder: placeholder.value } : {}) + } + })) + return { value, widget } + }, + template: '' + }) +} + +export const WithLabel: Story = { + render: () => ({ + components: { WidgetInputText }, + setup() { + const value = ref('Some value') + const widget: SimplifiedWidget = { + name: 'seed', + type: 'STRING', + value: '', + label: 'Random Seed' + } + return { value, widget } + }, + template: '' + }) +} diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue index eff22f1efc..b105747dd3 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue @@ -1,13 +1,28 @@ @@ -15,6 +30,7 @@ import InputText from 'primevue/inputtext' import { computed } from 'vue' +import Loader from '@/components/common/Loader.vue' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import { cn } from '@/utils/tailwindUtil' import { @@ -25,13 +41,34 @@ import { import { WidgetInputBaseClass } from './layout' import WidgetLayoutField from './layout/WidgetLayoutField.vue' -const props = defineProps<{ +const { + widget, + size = 'medium', + invalid = false, + loading = false +} = defineProps<{ widget: SimplifiedWidget + size?: 'medium' | 'large' + invalid?: boolean + loading?: boolean }>() const modelValue = defineModel({ default: '' }) const filteredProps = computed(() => - filterWidgetProps(props.widget.options, INPUT_EXCLUDED_PROPS) + filterWidgetProps(widget.options, INPUT_EXCLUDED_PROPS) ) + +const isReadOnly = computed(() => + Boolean(widget.options?.read_only || widget.options?.disabled) +) + +const layoutWidget = computed(() => ({ + name: widget.name, + label: widget.label, + borderStyle: cn( + widget.borderStyle, + invalid && 'border border-destructive-background' + ) +})) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.stories.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.stories.ts new file mode 100644 index 0000000000..b0e8916396 --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.stories.ts @@ -0,0 +1,130 @@ +import type { + ComponentPropsAndSlots, + Meta, + StoryObj +} from '@storybook/vue3-vite' +import { computed, provide, ref, toRefs } from 'vue' + +import type { SimplifiedWidget } from '@/types/simplifiedWidget' +import { HideLayoutFieldKey } from '@/types/widgetTypes' + +import WidgetTextarea from './WidgetTextarea.vue' + +interface StoryArgs extends ComponentPropsAndSlots { + readOnly: boolean + disabled: boolean +} + +const meta: Meta = { + title: 'Widgets/WidgetTextarea', + component: WidgetTextarea, + tags: ['autodocs'], + parameters: { + layout: 'centered', + docs: { + description: { + component: [ + 'Multi-line text input with optional label.', + 'Captures wheel, pointer, and context menu events to prevent canvas interference.', + 'Shows a copy-to-clipboard button on hover.' + ].join(' ') + } + } + }, + argTypes: { + readOnly: { control: 'boolean' }, + disabled: { control: 'boolean' } + }, + args: { + readOnly: false, + disabled: false + }, + decorators: [ + (story) => ({ + components: { story }, + template: '
' + }) + ] +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: (args) => ({ + components: { WidgetTextarea }, + setup() { + const { readOnly, disabled } = toRefs(args) + const value = ref('A multi-line text area for entering prompts.') + const widget = computed>(() => ({ + name: 'prompt', + type: 'STRING', + value: '', + label: 'Prompt', + options: { + read_only: readOnly.value, + disabled: disabled.value + } + })) + return { value, widget } + }, + template: + '' + }) +} + +export const Disabled: Story = { + args: { disabled: true }, + render: (args) => ({ + components: { WidgetTextarea }, + setup() { + const { disabled } = toRefs(args) + const value = ref('This textarea is disabled via widget options.') + const widget = computed>(() => ({ + name: 'locked', + type: 'STRING', + value: '', + label: 'Locked Field', + options: { disabled: disabled.value } + })) + return { value, widget } + }, + template: '' + }) +} + +export const HiddenLabel: Story = { + render: () => ({ + components: { WidgetTextarea }, + setup() { + provide(HideLayoutFieldKey, true) + const value = ref('Label is hidden via HideLayoutFieldKey injection.') + const widget: SimplifiedWidget = { + name: 'notes', + type: 'STRING', + value: '', + label: 'Notes' + } + return { value, widget } + }, + template: '' + }) +} + +export const WithPlaceholder: Story = { + render: () => ({ + components: { WidgetTextarea }, + setup() { + const value = ref('') + const widget: SimplifiedWidget = { + name: 'negative', + type: 'STRING', + value: '', + label: 'Negative Prompt' + } + return { value, widget } + }, + template: + '' + }) +} diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue index dcb42db6cc..8949b2a0ef 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue @@ -2,7 +2,7 @@ @@ -81,8 +81,8 @@ const filteredProps = computed(() => const displayName = computed(() => widget.label || widget.name) const id = useId() -const isReadOnly = computed( - () => widget.options?.read_only ?? widget.options?.disabled ?? false +const isReadOnly = computed(() => + Boolean(widget.options?.read_only || widget.options?.disabled) ) function handleCopy() { diff --git a/src/utils/widgetPropFilter.ts b/src/utils/widgetPropFilter.ts index d5925fbe67..f66a8e8bd3 100644 --- a/src/utils/widgetPropFilter.ts +++ b/src/utils/widgetPropFilter.ts @@ -17,7 +17,8 @@ export const STANDARD_EXCLUDED_PROPS = [ export const INPUT_EXCLUDED_PROPS = [ ...STANDARD_EXCLUDED_PROPS, 'inputClass', - 'inputStyle' + 'inputStyle', + 'read_only' ] as const export const PANEL_EXCLUDED_PROPS = [