Skip to content

Commit ac55bad

Browse files
Merge pull request #119 from wttech/field-descriptions
Field descriptions + type conversions fix
2 parents 28aaf95 + f4e47d0 commit ac55bad

File tree

9 files changed

+108
-54
lines changed

9 files changed

+108
-54
lines changed

core/src/main/java/dev/vml/es/acm/core/code/Argument.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public abstract class Argument<V> {
1414

1515
private String label;
1616

17+
private String description;
18+
1719
private String group = "general";
1820

1921
private boolean required = true;
@@ -59,6 +61,14 @@ public void setLabel(String label) {
5961
this.label = label;
6062
}
6163

64+
public String getDescription() {
65+
return description;
66+
}
67+
68+
public void setDescription(String description) {
69+
this.description = description;
70+
}
71+
6272
public boolean isRequired() {
6373
return required;
6474
}

core/src/main/java/dev/vml/es/acm/core/code/Arguments.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private <T> void setValue(Argument<T> argument, Object value) {
8787
if (valueType == null) {
8888
argument.setValue((T) value);
8989
} else {
90-
Optional<?> convertedValue = TypeUtils.convert(value, valueType);
90+
Optional<?> convertedValue = TypeUtils.convert(value, valueType, true);
9191
if (convertedValue.isPresent()) {
9292
argument.setValue((T) convertedValue.get());
9393
} else {

core/src/main/java/dev/vml/es/acm/core/util/TypeUtils.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ private TypeUtils() {
1616
}
1717

1818
/**
19-
* Convert value to the specified type. Fallback to implicit usage of {@link org.apache.sling.api.wrappers.impl.ObjectConverter}.
19+
* Convert a value to the specified type.
20+
* Fallback uses implicitly {@link org.apache.sling.api.wrappers.impl.ObjectConverter}.
2021
*/
21-
public static <T> Optional<T> convert(Object value, Class<T> type) {
22+
public static <T> Optional<T> convert(Object value, Class<T> type, boolean fallback) {
2223
if (value instanceof String) {
2324
if (type == LocalDateTime.class) {
2425
return Optional.ofNullable((T) DateUtils.toLocalDateTime((String) value));
@@ -49,7 +50,7 @@ public static <T> Optional<T> convert(Object value, Class<T> type) {
4950
}
5051
}
5152

52-
if (value != null && !type.isAssignableFrom(value.getClass())) {
53+
if (fallback && value != null && !type.isAssignableFrom(value.getClass())) {
5354
T convertedValue = new ValueMapDecorator(Collections.singletonMap("v", value)).get("v", type);
5455
if (convertedValue != null) {
5556
return Optional.of(convertedValue);

core/src/main/java/dev/vml/es/acm/core/util/TypeValueMap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ public TypeValueMap(Map<String, Object> base) {
1515

1616
@Override
1717
public <T> T get(String name, Class<T> type) {
18-
return TypeUtils.convert(get(name), type).orElse(null);
18+
return TypeUtils.convert(get(name), type, false).orElse(super.get(name, type));
1919
}
2020
}
Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
group: Repo
22
name: repo_demo
33
documentation: |
4-
Showcase of the `repo` code variable.
5-
Creates a folder structure and deletes it again.
4+
Showcase of the `repo` code variable. Creates a folder structure and deletes it again.
5+
6+
Documentation:
7+
- [Repo](https://github.com/wttech/acm/blob/main/core/src/main/java/dev/vml/es/acm/core/repo/Repo.java)
8+
- [RepoResource](https://github.com/wttech/acm/blob/main/core/src/main/java/dev/vml/es/acm/core/repo/RepoResource.java)
69
content: |
10+
void describeRun() {
11+
args.bool("dryRun") { value = true; switcher(); description = "Do not commit changes to the repository" }
12+
args.bool("clean") { value = true; switcher(); description = "Finally delete all created resources" }
13+
}
14+
715
boolean canRun() {
816
return condition.idleSelf()
917
}
10-
18+
1119
void doRun() {
1220
out.fromLogs()
1321
14-
println "Creating a folder structure in the temporary directory of the repository."
22+
if (args.value("dryRun")) {
23+
repo.autoCommit = false
24+
println "Running in dry run mode. No changes will be committed to the repository."
25+
} else {
26+
repo.autoCommit = true
27+
println "Running in normal mode. Changes will be committed to the repository."
28+
}
1529
30+
println "Creating a folder structure in the temporary directory of the repository."
1631
def folder = repo.get("/tmp/acm/demo").ensureFolder()
1732
for (int i = 0; i < 5; i++) {
1833
def child = folder.child("child-\${i+1}")
@@ -21,5 +36,7 @@ content: |
2136
2237
println "Folder '${folder.path}' has now ${folder.descendants().count()} descendant(s)."
2338
24-
folder.delete()
39+
if (args.value("clean")) {
40+
folder.delete()
41+
}
2542
}

ui.frontend/src/components/CodeArgumentInput.module.css

Lines changed: 0 additions & 9 deletions
This file was deleted.

ui.frontend/src/components/CodeArgumentInput.tsx

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
ColorPicker,
88
DateField,
99
DatePicker,
10-
Flex,
1110
Item,
1211
ListView,
1312
NumberField,
@@ -44,8 +43,8 @@ import {
4443
} from '../utils/api.types.ts';
4544
import { Dates } from '../utils/dates';
4645
import { Strings } from '../utils/strings';
47-
import styles from './CodeArgumentInput.module.css';
48-
import PathField from './PathPicker.tsx';
46+
import Markdown from './Markdown';
47+
import PathField from './PathPicker';
4948

5049
interface CodeArgumentInputProps {
5150
arg: Argument<ArgumentValue>;
@@ -56,6 +55,7 @@ interface CodeArgumentInputProps {
5655
const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
5756
const { control, controllerRules } = useArgumentInput(arg);
5857
const label = arg.label || Strings.capitalizeWords(arg.name);
58+
const description = arg.description ? <Markdown>{arg.description}</Markdown> : undefined;
5959

6060
return (
6161
<Controller
@@ -67,18 +67,19 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
6767
{(() => {
6868
if (isBoolArgument(arg)) {
6969
return (
70-
<Flex alignItems={'start'} justifyContent={'start'} direction={'column'}>
71-
{arg.display === 'SWITCHER' ? (
72-
<Switch {...field} isSelected={field.value} onChange={field.onChange} aria-label={`Argument '${arg.name}'`}>
73-
{label}
74-
</Switch>
75-
) : (
76-
<Checkbox {...field} isSelected={field.value} isInvalid={!!fieldState.error} onChange={field.onChange} aria-label={`Argument '${arg.name}'`}>
77-
{label}
78-
</Checkbox>
79-
)}
80-
{fieldState.error && <p className={styles.error}>{fieldState.error.message}</p>}
81-
</Flex>
70+
<Field description={description} width="100%" errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'}>
71+
<div>
72+
{arg.display === 'SWITCHER' ? (
73+
<Switch {...field} isSelected={field.value} onChange={field.onChange} aria-label={`Argument '${arg.name}'`}>
74+
{label}
75+
</Switch>
76+
) : (
77+
<Checkbox {...field} isSelected={field.value} isInvalid={!!fieldState.error} onChange={field.onChange} aria-label={`Argument '${arg.name}'`}>
78+
{label}
79+
</Checkbox>
80+
)}
81+
</div>
82+
</Field>
8283
);
8384
} else if (isDateTimeArgument(arg)) {
8485
return arg.type === 'DATE' ? (
@@ -89,6 +90,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
8990
maxValue={arg.max !== null ? Dates.toCalendarDate(arg.max) : undefined}
9091
onChange={(dateValue) => field.onChange(dateValue?.toString())}
9192
label={label}
93+
description={description}
9294
errorMessage={fieldState.error ? fieldState.error.message : undefined}
9395
validationState={fieldState.error ? 'invalid' : 'valid'}
9496
aria-label={`Argument '${arg.name}'`}
@@ -101,6 +103,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
101103
maxValue={arg.max !== null ? Dates.toTime(arg.max) : undefined}
102104
onChange={(timeValue) => field.onChange(timeValue?.toString())}
103105
label={label}
106+
description={description}
104107
errorMessage={fieldState.error ? fieldState.error.message : undefined}
105108
validationState={fieldState.error ? 'invalid' : 'valid'}
106109
aria-label={`Argument '${arg.name}'`}
@@ -114,6 +117,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
114117
onChange={(dateValue) => field.onChange(dateValue?.toString())}
115118
granularity="second"
116119
label={label}
120+
description={description}
117121
errorMessage={fieldState.error ? fieldState.error.message : undefined}
118122
validationState={fieldState.error ? 'invalid' : 'valid'}
119123
aria-label={`Argument '${arg.name}'`}
@@ -125,6 +129,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
125129
type={arg.display}
126130
{...field}
127131
label={label}
132+
description={description}
128133
errorMessage={fieldState.error ? fieldState.error.message : undefined}
129134
validationState={fieldState.error ? 'invalid' : 'valid'}
130135
aria-label={`Argument '${arg.name}'`}
@@ -133,7 +138,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
133138
);
134139
} else if (isTextArgument(arg)) {
135140
return arg.language ? (
136-
<Field label={label} description={`Language: ${arg.language}`} width="100%" errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'}>
141+
<Field label={label} description={description} width="100%" errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'}>
137142
<div>
138143
<View width="100%" backgroundColor="gray-800" borderWidth="thin" position="relative" borderColor="dark" height="100%" borderRadius="medium" padding="size-50">
139144
<Editor aria-label={`Argument '${arg.name}'`} language={arg.language} theme="vs-dark" height="200px" options={{ scrollBeyondLastLine: false }} value={field.value?.toString() || ''} onChange={field.onChange} />
@@ -152,6 +157,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
152157
{...field}
153158
orientation="horizontal"
154159
label={label}
160+
description={description}
155161
errorMessage={fieldState.error ? fieldState.error.message : undefined}
156162
validationState={fieldState.error ? 'invalid' : 'valid'}
157163
aria-label={`Argument '${arg.name}'`}
@@ -166,6 +172,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
166172
<Picker
167173
{...field}
168174
label={label}
175+
description={description}
169176
selectedKey={field.value?.toString() || ''}
170177
onSelectionChange={field.onChange}
171178
errorMessage={fieldState.error ? fieldState.error.message : undefined}
@@ -186,6 +193,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
186193
{...field}
187194
orientation="horizontal"
188195
label={label}
196+
description={description}
189197
errorMessage={fieldState.error ? fieldState.error.message : undefined}
190198
validationState={fieldState.error ? 'invalid' : 'valid'}
191199
aria-label={`Argument '${arg.name}'`}
@@ -197,7 +205,7 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
197205
))}
198206
</CheckboxGroup>
199207
) : (
200-
<Field label={label} width="100%" errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'}>
208+
<Field label={label} description={description} width="100%" errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'}>
201209
<div>
202210
<ListView
203211
{...field}
@@ -217,16 +225,18 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
217225
} else if (isNumberArgument(arg)) {
218226
if (arg.display === 'SLIDER') {
219227
return (
220-
<Flex justifyContent={'start'} alignItems={'start'} direction={'column'}>
221-
<Slider {...field} label={label} minValue={arg.min !== null ? arg.min : 0} maxValue={arg.max !== null ? arg.max : 100} step={arg.step ? arg.step : 1} aria-label={`Argument '${arg.name}'`} />
222-
{fieldState.error && <p className={styles.error}>{fieldState.error.message}</p>}
223-
</Flex>
228+
<Field description={description} width="100%" errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'}>
229+
<div>
230+
<Slider {...field} label={label} minValue={arg.min !== null ? arg.min : 0} maxValue={arg.max !== null ? arg.max : 100} step={arg.step ? arg.step : 1} aria-label={`Argument '${arg.name}'`} />
231+
</div>
232+
</Field>
224233
);
225234
} else {
226235
return (
227236
<NumberField
228237
{...field}
229238
label={label}
239+
description={description}
230240
errorMessage={fieldState.error ? fieldState.error.message : undefined}
231241
validationState={fieldState.error ? 'invalid' : 'valid'}
232242
minValue={arg.min !== null ? arg.min : undefined}
@@ -239,26 +249,42 @@ const CodeArgumentInput: React.FC<CodeArgumentInputProps> = ({ arg }) => {
239249
}
240250
} else if (isColorArgument(arg)) {
241251
return (
242-
<Flex justifyContent={'start'} alignItems={'start'} direction={'column'}>
243-
<ColorPicker {...field} label={label} aria-label={`Argument '${arg.name}'`} value={field.value ? parseColor(field.value) : ''} onChange={(value) => field.onChange(value.toString(arg.format.toLowerCase() as ColorFormat))}>
244-
<ColorEditor hideAlphaChannel={arg.format !== 'RGBA'} />
245-
<Button onPress={() => field.onChange('')} width={'100%'} marginTop={'size-200'} variant={'secondary'}>
246-
Clear
247-
</Button>
248-
</ColorPicker>
249-
{fieldState.error && <p className={styles.error}>{fieldState.error.message}</p>}
250-
</Flex>
252+
<Field description={description} width="100%" errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'}>
253+
<div>
254+
<ColorPicker
255+
{...field}
256+
label={label}
257+
aria-label={`Argument '${arg.name}'`}
258+
value={field.value ? parseColor(field.value) : ''}
259+
onChange={(value) => field.onChange(value.toString(arg.format.toLowerCase() as ColorFormat))}
260+
>
261+
<ColorEditor hideAlphaChannel={arg.format !== 'RGBA'} />
262+
<Button onPress={() => field.onChange('')} width={'100%'} marginTop={'size-200'} variant={'secondary'}>
263+
Clear
264+
</Button>
265+
</ColorPicker>
266+
</div>
267+
</Field>
251268
);
252269
} else if (isRangeArgument(arg)) {
253270
return (
254-
<Flex justifyContent={'start'} alignItems={'start'} direction={'column'}>
255-
<RangeSlider {...field} label={label} minValue={arg.min !== null ? arg.min : 0} maxValue={arg.max !== null ? arg.max : 100} step={arg.step ? arg.step : 1} aria-label={`Argument '${arg.name}'`} />
256-
{fieldState.error && <p className={styles.error}>{fieldState.error.message}</p>}
257-
</Flex>
271+
<Field description={description} width="100%" errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'}>
272+
<div>
273+
<RangeSlider {...field} label={label} minValue={arg.min !== null ? arg.min : 0} maxValue={arg.max !== null ? arg.max : 100} step={arg.step ? arg.step : 1} aria-label={`Argument '${arg.name}'`} />
274+
</div>
275+
</Field>
258276
);
259277
} else if (isPathArgument(arg)) {
260278
return (
261-
<PathField label={label} root={arg.root} onSelect={field.onChange} value={field.value ?? ''} errorMessage={fieldState.error ? fieldState.error.message : undefined} validationState={fieldState.error ? 'invalid' : 'valid'} />
279+
<PathField
280+
label={label}
281+
description={description}
282+
root={arg.root}
283+
onSelect={field.onChange}
284+
value={field.value ?? ''}
285+
errorMessage={fieldState.error ? fieldState.error.message : undefined}
286+
validationState={fieldState.error ? 'invalid' : 'valid'}
287+
/>
262288
);
263289
} else {
264290
throw new Error(`Unsupported argument type: ${arg.type}`);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
.markdown code {
22
font-size: 12px;
33
}
4+
5+
.markdown p:first-child {
6+
margin-block-start: 0;
7+
}
8+
9+
.markdown p:last-child {
10+
margin-block-end: 0;
11+
}

ui.frontend/src/utils/api.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export type Argument<T> = {
3838
type: ArgumentType;
3939
value: T;
4040
label: string;
41+
description?: string;
4142
required: boolean;
4243
group: string;
4344
validator?: string;

0 commit comments

Comments
 (0)