Skip to content

Commit

Permalink
Improve creator UI
Browse files Browse the repository at this point in the history
  • Loading branch information
randomnetcat authored and florkbr committed Aug 8, 2024
1 parent 36c6c77 commit 7a573a7
Show file tree
Hide file tree
Showing 17 changed files with 1,068 additions and 480 deletions.
12 changes: 12 additions & 0 deletions src/Creator.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.rc-header {
background-color: var(--pf-v5-global--BackgroundColor--dark-300);
color: var(--pf-v5-global--Color--light-100);
}

.rc-header a {
color: var(--pf-v5-global--link--Color--light);
}

.rc-header a:hover {
color: var(--pf-v5-global--link--Color--light--hover);
}
161 changes: 67 additions & 94 deletions src/Creator.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,40 @@
import React, { useMemo, useState } from 'react';
import YAML, { YAMLError } from 'yaml';
import YAML from 'yaml';
import {
Grid,
GridItem,
PageGroup,
PageSection,
Title,
} from '@patternfly/react-core';
import {
QuickStart,
QuickStartSpec,
QuickStartTask,
} from '@patternfly/quickstarts';
import { QuickStart, QuickStartSpec } from '@patternfly/quickstarts';
import CreatorWizard, { EMPTY_TASK } from './components/creator/CreatorWizard';
import { ItemKind, metaForKind } from './components/creator/meta';
import CreatorPreview from './components/creator/CreatorPreview';

export type CreatorErrors = {
taskErrors: Map<number, string>;
};
import './Creator.scss';
import { CreatorWizardStage } from './components/creator/schema';

const BASE_METADATA = {
name: 'test-quickstart',
};

function makeDemoQuickStart(
kind: ItemKind | null,
baseQuickStart: QuickStart,
taskContents: string[]
): [QuickStart, CreatorErrors] {
baseQuickStart: QuickStart
): QuickStart {
const kindMeta = kind !== null ? metaForKind(kind) : null;

const [tasks, taskErrors] = (() => {
if (kindMeta?.hasTasks !== true) return [undefined, new Map()];

const out: QuickStartTask[] = [];
const errors: CreatorErrors['taskErrors'] = new Map();

if (baseQuickStart.spec.tasks !== undefined) {
for (let index = 0; index < baseQuickStart.spec.tasks.length; ++index) {
const task = baseQuickStart.spec.tasks[index];

try {
out.push({
...YAML.parse(taskContents[index]),
title: task.title,
});
} catch (e) {
if (!(e instanceof YAMLError)) throw e;

out.push({ ...EMPTY_TASK, title: task.title });
errors.set(index, e.message);
}
}
}

return [out, errors];
})();

return [
{
...baseQuickStart,
metadata: {
...baseQuickStart.metadata,
name: 'test-quickstart',
...(kindMeta?.extraMetadata ?? {}),
},
spec: {
...baseQuickStart.spec,
tasks: tasks,
},
return {
...baseQuickStart,
metadata: {
...baseQuickStart.metadata,
name: 'test-quickstart',
...(kindMeta?.extraMetadata ?? {}),
},
{ taskErrors },
];
};
}

const Creator = () => {
const CreatorInternal = ({ resetCreator }: { resetCreator: () => void }) => {
const [rawKind, setRawKind] = useState<ItemKind | null>(null);

const [rawQuickStart, setRawQuickStart] = useState<QuickStart>({
Expand All @@ -93,9 +52,11 @@ const Creator = () => {
rawKind !== null ? { id: rawKind, meta: metaForKind(rawKind) } : null;

const [bundles, setBundles] = useState<string[]>([]);
const [taskContents, setTaskContents] = useState<string[]>([]);
const [currentStage, setCurrentStage] = useState<CreatorWizardStage>({
type: 'card',
});

const [currentTask, setCurrentTask] = useState<number | null>(null);
const isDownloadStage = currentStage.type !== 'download';

const updateSpec = (
updater: (old: QuickStartSpec) => Partial<QuickStartSpec>
Expand Down Expand Up @@ -143,20 +104,14 @@ const Creator = () => {

return { ...old, ...updates };
});

if (meta.hasTasks) {
setTaskContents((old) => (old.length === 0 ? [''] : old));
} else if (!meta.hasTasks) {
setTaskContents([]);
}
}

setRawKind(newKind);
};

const [quickStart, errors] = useMemo(
() => makeDemoQuickStart(rawKind, rawQuickStart, taskContents),
[rawKind, rawQuickStart, taskContents]
const quickStart = useMemo(
() => makeDemoQuickStart(rawKind, rawQuickStart),
[rawKind, rawQuickStart]
);

const files = useMemo(() => {
Expand All @@ -165,15 +120,18 @@ const Creator = () => {
.replaceAll(/\s/g, '-')
.replaceAll(/(^-+)|(-+$)/g, '');

const adjustedQuickstart = { ...quickStart };
adjustedQuickstart.spec = { ...adjustedQuickstart.spec };
adjustedQuickstart.metadata = {
...adjustedQuickstart.metadata,
name: effectiveName,
const adjustedQuickstart = {
...quickStart,
spec: {
...quickStart.spec,
icon: undefined,
},
metadata: {
...quickStart.metadata,
name: effectiveName,
},
};

delete adjustedQuickstart.spec['icon'];

return [
{
name: 'metadata.yaml',
Expand All @@ -192,49 +150,64 @@ const Creator = () => {
];
}, [quickStart, bundles]);

if ((quickStart.spec.tasks?.length ?? 0) != taskContents.length) {
throw new Error(
`Mismatch between quickstart tasks and task contents: ${quickStart.spec.tasks?.length} vs ${taskContents.length}`
);
}

return (
<PageGroup>
<PageSection variant="darker">
<PageSection className="rc-header">
<Title headingLevel="h1" size="2xl">
Add new learning resources
Add new learning resource
</Title>

<p>Description</p>
<p>
Add cards to the learning resources spaces within console.redhat.com.{' '}
<a
href="https://docs.google.com/presentation/d/1FiwBc_VuCxvobv80suXww0eKEs381MgR1WK6q7_DynY/edit#slide=id.g1b95fa54a9f_0_801"
target="_blank"
rel="noreferrer"
>
Learn more about Hybrid Cloud Console Learning Resources.
</a>
</p>
</PageSection>

<PageSection isFilled>
<PageSection isFilled padding={{ default: 'noPadding' }}>
<Grid hasGutter className="pf-v5-u-h-100 pf-v5-u-w-100">
<GridItem span={12} lg={6}>
<GridItem span={12} lg={isDownloadStage ? 6 : 12}>
<CreatorWizard
onChangeKind={setKind}
onChangeQuickStartSpec={(spec) => {
updateSpec(() => spec);
}}
onChangeBundles={setBundles}
onChangeTaskContents={setTaskContents}
onChangeCurrentTask={setCurrentTask}
errors={errors}
onChangeCurrentStage={setCurrentStage}
resetCreator={resetCreator}
files={files}
/>
</GridItem>

<GridItem span={12} lg={6}>
<CreatorPreview
kindMeta={selectedKind?.meta ?? null}
quickStart={quickStart}
currentTask={currentTask}
/>
</GridItem>
{isDownloadStage ? (
<GridItem span={12} lg={6} className="pf-v5-u-pt-md-on-lg">
<CreatorPreview
kindMeta={selectedKind?.meta ?? null}
quickStart={quickStart}
currentStage={currentStage}
/>
</GridItem>
) : null}
</Grid>
</PageSection>
</PageGroup>
);
};

const Creator = () => {
const [resetCount, setResetCount] = useState(0n);

return (
<CreatorInternal
key={resetCount}
resetCreator={() => setResetCount((old) => old + 1n)}
/>
);
};

export default Creator;
62 changes: 62 additions & 0 deletions src/components/DdfNumberInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
FormGroup,
FormHelperText,
HelperText,
HelperTextItem,
NumberInput,
} from '@patternfly/react-core';
import React, { useId } from 'react';
import {
UseFieldApiConfig,
useFieldApi,
} from '@data-driven-forms/react-form-renderer';
import ExclamationCircleIcon from '@patternfly/react-icons/dist/dynamic/icons/exclamation-circle-icon';

const DdfNumberInput = (props: UseFieldApiConfig) => {
const { input, meta, ...rest } = useFieldApi(props);
const reactId = useId();
const effectiveId = rest.id || reactId;

const showInvalid = meta.touched && meta.invalid;

const focusProps = {
onFocus: input.onFocus,
onBlur: input.onBlur,
};

return (
<FormGroup
label={rest.label}
isRequired={rest.isRequired}
fieldId={effectiveId}
>
<NumberInput
id={effectiveId}
inputName={input.name}
value={input.value}
validated={showInvalid ? 'error' : 'default'}
min={rest.minValue}
max={rest.maxValue}
unit={rest.unit}
onPlus={() => input.onChange((input.value ?? 0) + 1)}
onMinus={() => input.onChange((input.value ?? 0) - 1)}
onChange={(e) => input.onChange(e.currentTarget.valueAsNumber)}
{...focusProps}
plusBtnProps={focusProps}
minusBtnProps={focusProps}
/>

{showInvalid ? (
<FormHelperText>
<HelperText>
<HelperTextItem variant={'error'} icon={<ExclamationCircleIcon />}>
{meta.error}
</HelperTextItem>
</HelperText>
</FormHelperText>
) : null}
</FormGroup>
);
};

export default DdfNumberInput;
26 changes: 26 additions & 0 deletions src/components/SimpleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { ButtonHTMLAttributes, ReactNode } from 'react';

const SimpleButton = ({
type = 'button',
children,
icon,
...rest
}: ButtonHTMLAttributes<HTMLButtonElement> & {
icon?: ReactNode;
}) => {
return (
<button
{...rest}
type={type}
className={`pf-v5-u-background-color-200 ${rest.className ?? ''}`}
style={{ border: 'none', ...(rest.style ?? {}) }}
>
<span className={icon !== undefined ? 'pf-v5-u-mr-sm' : ''}>
{children}
</span>
{icon}
</button>
);
};

export default SimpleButton;
14 changes: 14 additions & 0 deletions src/components/StringArrayInput.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.lr-string-array {
display: grid;
grid-template-columns: [label-start] auto [label-end value-start] 1fr [value-end remove-start] auto [remove-end];

.lr-string-array-label {
grid-column-start: label-start;
grid-column-end: label-end;
}

.lr-string-array-value {
grid-column-start: value-start;
grid-column-end: value-end;
}
}
Loading

0 comments on commit 7a573a7

Please sign in to comment.