mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-28 12:11:53 +01:00
OAuth 2 (#158)
This commit is contained in:
@@ -7,6 +7,7 @@ import type {
|
||||
FormInputHttpRequest,
|
||||
FormInputSelect,
|
||||
FormInputText,
|
||||
JsonPrimitive,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback } from 'react';
|
||||
@@ -15,51 +16,93 @@ import { useFolders } from '../hooks/useFolders';
|
||||
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||
import { capitalize } from '../lib/capitalize';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import { Editor } from './core/Editor/Editor';
|
||||
import { Input } from './core/Input';
|
||||
import { Label } from './core/Label';
|
||||
import { Select } from './core/Select';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { Markdown } from './Markdown';
|
||||
import { SelectFile } from './SelectFile';
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const DYNAMIC_FORM_NULL_ARG = '__NULL__';
|
||||
const INPUT_SIZE = 'sm';
|
||||
|
||||
export function DynamicForm<T extends Record<string, string | boolean>>({
|
||||
config,
|
||||
data,
|
||||
onChange,
|
||||
useTemplating,
|
||||
autocompleteVariables,
|
||||
stateKey,
|
||||
}: {
|
||||
config: FormInput[];
|
||||
interface Props<T> {
|
||||
inputs: FormInput[] | undefined | null;
|
||||
onChange: (value: T) => void;
|
||||
data: T;
|
||||
useTemplating?: boolean;
|
||||
autocompleteVariables?: boolean;
|
||||
stateKey: string;
|
||||
}) {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function DynamicForm<T extends Record<string, JsonPrimitive>>({
|
||||
inputs,
|
||||
data,
|
||||
onChange,
|
||||
useTemplating,
|
||||
autocompleteVariables,
|
||||
stateKey,
|
||||
disabled,
|
||||
}: Props<T>) {
|
||||
const setDataAttr = useCallback(
|
||||
(name: string, value: string | boolean | null) => {
|
||||
(name: string, value: JsonPrimitive) => {
|
||||
onChange({ ...data, [name]: value == DYNAMIC_FORM_NULL_ARG ? undefined : value });
|
||||
},
|
||||
[data, onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<FormInputs
|
||||
disabled={disabled}
|
||||
inputs={inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
useTemplating={useTemplating}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
inputs,
|
||||
autocompleteVariables,
|
||||
stateKey,
|
||||
useTemplating,
|
||||
setDataAttr,
|
||||
data,
|
||||
disabled,
|
||||
}: Pick<Props<T>, 'inputs' | 'useTemplating' | 'autocompleteVariables' | 'stateKey' | 'data'> & {
|
||||
setDataAttr: (name: string, value: JsonPrimitive) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<VStack space={3} className="h-full overflow-auto">
|
||||
{config.map((a, i) => {
|
||||
switch (a.type) {
|
||||
{inputs?.map((input, i) => {
|
||||
if ('hidden' in input && input.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('disabled' in input && disabled != null) {
|
||||
input.disabled = disabled;
|
||||
}
|
||||
|
||||
switch (input.type) {
|
||||
case 'select':
|
||||
return (
|
||||
<SelectArg
|
||||
key={i + stateKey}
|
||||
arg={a}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={
|
||||
data[a.name] ? String(data[a.name]) : (a.defaultValue ?? DYNAMIC_FORM_NULL_ARG)
|
||||
data[input.name]
|
||||
? String(data[input.name])
|
||||
: (input.defaultValue ?? DYNAMIC_FORM_NULL_ARG)
|
||||
}
|
||||
/>
|
||||
);
|
||||
@@ -68,11 +111,13 @@ export function DynamicForm<T extends Record<string, string | boolean>>({
|
||||
<TextArg
|
||||
key={i}
|
||||
stateKey={stateKey}
|
||||
arg={a}
|
||||
arg={input}
|
||||
useTemplating={useTemplating || false}
|
||||
autocompleteVariables={autocompleteVariables || false}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
value={data[a.name] ? String(data[a.name]) : (a.defaultValue ?? '')}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={
|
||||
data[input.name] != null ? String(data[input.name]) : (input.defaultValue ?? '')
|
||||
}
|
||||
/>
|
||||
);
|
||||
case 'editor':
|
||||
@@ -80,40 +125,79 @@ export function DynamicForm<T extends Record<string, string | boolean>>({
|
||||
<EditorArg
|
||||
key={i}
|
||||
stateKey={stateKey}
|
||||
arg={a}
|
||||
arg={input}
|
||||
useTemplating={useTemplating || false}
|
||||
autocompleteVariables={autocompleteVariables || false}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
value={data[a.name] ? String(data[a.name]) : (a.defaultValue ?? '')}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={
|
||||
data[input.name] != null ? String(data[input.name]) : (input.defaultValue ?? '')
|
||||
}
|
||||
/>
|
||||
);
|
||||
case 'checkbox':
|
||||
return (
|
||||
<CheckboxArg
|
||||
key={i + stateKey}
|
||||
arg={a}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
value={data[a.name] !== undefined ? data[a.name] === true : false}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={data[input.name] != null ? data[input.name] === true : false}
|
||||
/>
|
||||
);
|
||||
case 'http_request':
|
||||
return (
|
||||
<HttpRequestArg
|
||||
key={i + stateKey}
|
||||
arg={a}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
value={data[a.name] ? String(data[a.name]) : DYNAMIC_FORM_NULL_ARG}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={data[input.name] != null ? String(data[input.name]) : DYNAMIC_FORM_NULL_ARG}
|
||||
/>
|
||||
);
|
||||
case 'file':
|
||||
return (
|
||||
<FileArg
|
||||
key={i + stateKey}
|
||||
arg={a}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
filePath={data[a.name] ? String(data[a.name]) : DYNAMIC_FORM_NULL_ARG}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
filePath={
|
||||
data[input.name] != null ? String(data[input.name]) : DYNAMIC_FORM_NULL_ARG
|
||||
}
|
||||
/>
|
||||
);
|
||||
case 'accordion':
|
||||
return (
|
||||
<Banner key={i} className={classNames('!p-0', disabled && 'opacity-disabled')}>
|
||||
<details>
|
||||
<summary className="px-3 py-1.5 text-text-subtle">{input.label}</summary>
|
||||
<div className="mb-3 px-3">
|
||||
<FormInputs
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
</Banner>
|
||||
);
|
||||
case 'banner':
|
||||
return (
|
||||
<Banner
|
||||
key={i}
|
||||
color={input.color}
|
||||
className={classNames(disabled && 'opacity-disabled')}
|
||||
>
|
||||
<FormInputs
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
/>
|
||||
</Banner>
|
||||
);
|
||||
case 'markdown':
|
||||
return <Markdown>{input.content}</Markdown>;
|
||||
}
|
||||
})}
|
||||
</VStack>
|
||||
@@ -145,13 +229,17 @@ function TextArg({
|
||||
return (
|
||||
<Input
|
||||
name={arg.name}
|
||||
multiLine={arg.multiLine}
|
||||
onChange={handleChange}
|
||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
||||
required={!arg.optional}
|
||||
disabled={arg.disabled}
|
||||
type={arg.password ? 'password' : 'text'}
|
||||
label={arg.label ?? arg.name}
|
||||
size={INPUT_SIZE}
|
||||
hideLabel={arg.label == null}
|
||||
placeholder={arg.placeholder ?? arg.defaultValue ?? ''}
|
||||
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
|
||||
useTemplating={useTemplating}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
stateKey={stateKey}
|
||||
@@ -184,23 +272,29 @@ function EditorArg({
|
||||
|
||||
const id = `input-${arg.name}`;
|
||||
|
||||
// Read-only editor force refresh for every defaultValue change
|
||||
// Should this be built into the <Editor/> component?
|
||||
const forceUpdateKey = arg.readOnly ? arg.defaultValue + stateKey : stateKey;
|
||||
|
||||
return (
|
||||
<div className=" w-full grid grid-cols-1 grid-rows-[auto_minmax(0,1fr)]">
|
||||
<Label
|
||||
htmlFor={id}
|
||||
optional={arg.optional}
|
||||
required={!arg.optional}
|
||||
visuallyHidden={arg.hideLabel}
|
||||
otherTags={arg.language ? [capitalize(arg.language)] : undefined}
|
||||
tags={arg.language ? [capitalize(arg.language)] : undefined}
|
||||
>
|
||||
{arg.label}
|
||||
</Label>
|
||||
<Editor
|
||||
id={id}
|
||||
className={classNames(
|
||||
'border border-border rounded-md overflow-hidden px-2 py-1.5',
|
||||
'border border-border rounded-md overflow-hidden px-2 py-1',
|
||||
'focus-within:border-border-focus',
|
||||
'max-h-[15rem]', // So it doesn't take up too much space
|
||||
)}
|
||||
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
|
||||
disabled={arg.disabled}
|
||||
language={arg.language}
|
||||
onChange={handleChange}
|
||||
heightMode="auto"
|
||||
@@ -209,7 +303,7 @@ function EditorArg({
|
||||
useTemplating={useTemplating}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
stateKey={stateKey}
|
||||
forceUpdateKey={stateKey}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
hideGutter
|
||||
/>
|
||||
</div>
|
||||
@@ -232,12 +326,9 @@ function SelectArg({
|
||||
onChange={onChange}
|
||||
hideLabel={arg.hideLabel}
|
||||
value={value}
|
||||
options={[
|
||||
...arg.options.map((a) => ({
|
||||
label: a.name,
|
||||
value: a.value,
|
||||
})),
|
||||
]}
|
||||
size={INPUT_SIZE}
|
||||
disabled={arg.disabled}
|
||||
options={arg.options}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -253,6 +344,7 @@ function FileArg({
|
||||
}) {
|
||||
return (
|
||||
<SelectFile
|
||||
disabled={arg.disabled}
|
||||
onChange={({ filePath }) => onChange(filePath)}
|
||||
filePath={filePath === '__NULL__' ? null : filePath}
|
||||
directory={!!arg.directory}
|
||||
@@ -278,6 +370,7 @@ function HttpRequestArg({
|
||||
name={arg.name}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
disabled={arg.disabled}
|
||||
options={[
|
||||
...httpRequests.map((r) => {
|
||||
return {
|
||||
@@ -323,6 +416,7 @@ function CheckboxArg({
|
||||
<Checkbox
|
||||
onChange={onChange}
|
||||
checked={value}
|
||||
disabled={arg.disabled}
|
||||
title={arg.label ?? arg.name}
|
||||
hideLabel={arg.label == null}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user