Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#9492 Add database to Rich Text Editor form builder field #9495

Merged
merged 7 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type SelectStringOption,
type SetActiveField,
} from "@/components/formBuilder/formBuilderTypes";
import FieldEditor from "./FieldEditor";
import FieldEditor from "./fieldEditor/FieldEditor";
import {
moveStringInArray,
getNormalizedUiOrder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import SelectWidget, {
import SwitchButtonWidget, {
type CheckBoxLike,
} from "@/components/form/widgets/switchButton/SwitchButtonWidget";
import { uniq, partial } from "lodash";
import { uniq } from "lodash";
import { type SchemaFieldProps } from "@/components/fields/schemaFields/propTypes";
import SchemaField from "@/components/fields/schemaFields/SchemaField";
import databaseSchema from "@schemas/database.json";
Expand All @@ -55,9 +55,10 @@ import {
import { type Schema, type SchemaPropertyType } from "@/types/schemaTypes";
import { AnnotationType } from "@/types/annotationTypes";
import { isNullOrBlank } from "@/utils/stringUtils";
import { Collapse } from "react-bootstrap";
import { joinName, joinPathParts } from "@/utils/formUtils";
import { joinPathParts } from "@/utils/formUtils";
import { assertNotNullish } from "@/utils/nullishUtils";
import TextAreaFields from "@/components/formBuilder/edit/fieldEditor/TextAreaFields";
import RichTextFields from "@/components/formBuilder/edit/fieldEditor/RichTextFields";

const imageForCroppingSourceSchema: Schema = {
type: "string",
Expand Down Expand Up @@ -95,60 +96,6 @@ function shouldShowPlaceholderText(uiType: UiType): boolean {
}
}

const TextAreaFields: React.FC<{ uiOptionsPath: string }> = ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

uiOptionsPath,
}) => {
const configName = partial(joinName, uiOptionsPath);
const [{ value: showSubmitToolbar }] = useField<boolean | null>(
configName("submitToolbar", "show"),
);

return (
<>
<SchemaField
name={configName("rows")}
schema={{
type: "number",
title: "# Rows",
description:
"The number of visible text lines for the control. If it is not specified, the default value is 2.",
}}
/>
<SchemaField
name={configName("submitOnEnter")}
schema={{
type: "boolean",
title: "Submit Form on Enter?",
description:
"If enabled, pressing Enter will submit the form. Press Shift+Enter for newlines in this mode",
}}
isRequired
/>
<SchemaField
name={configName("submitToolbar", "show")}
schema={{
type: "boolean",
title: "Include Submit Toolbar?",
description:
"Enable the submit toolbar that has a selectable icon to act as a submit button",
}}
isRequired
/>
<Collapse in={showSubmitToolbar ?? false}>
<SchemaField
name={configName("submitToolbar", "icon")}
schema={{ $ref: "https://app.pixiebrix.com/schemas/icon#" }}
label="Select Icon"
description="Select the icon that appears in the bottom right of the Submit Toolbar"
uiSchema={{
"ui:widget": "IconWidget",
}}
/>
</Collapse>
</>
);
};

const FieldEditor: React.FC<{
name: string;
propertyName: string;
Expand Down Expand Up @@ -392,6 +339,17 @@ const FieldEditor: React.FC<{
onChange={onUiTypeChange}
/>

{uiType.uiWidget === "richText" && (
<RichTextFields
uiOptionsPath={joinPathParts(
name,
"uiSchema",
propertyName,
"ui:options",
)}
/>
)}

{uiType.uiWidget === "imageCrop" && (
<SchemaField
label="Image source"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React from "react";
import { partial } from "lodash";
import { joinName } from "@/utils/formUtils";
import SchemaField from "@/components/fields/schemaFields/SchemaField";

const RichTextFields: React.FunctionComponent<{ uiOptionsPath: string }> = ({
uiOptionsPath,
}) => {
const configName = partial(joinName, uiOptionsPath);

return (
<SchemaField
name={configName("database")}
isRequired
schema={{
$ref: "https://app.pixiebrix.com/schemas/database#",
title: "Asset Database",
description:
"Asset database to use for image upload. Asset databases are a specific kind of database that" +
" can be created in the Admin Console.",
}}
uiSchema={{
"ui:widget": "database",
}}
/>
);
};

export default RichTextFields;
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React from "react";
import { partial } from "lodash";
import { joinName } from "@/utils/formUtils";
import { useField } from "formik";
import SchemaField from "@/components/fields/schemaFields/SchemaField";
import { Collapse } from "react-bootstrap";

const TextAreaFields: React.FC<{ uiOptionsPath: string }> = ({
uiOptionsPath,
}) => {
const configName = partial(joinName, uiOptionsPath);
const [{ value: showSubmitToolbar }] = useField<boolean | null>(
configName("submitToolbar", "show"),
);

return (
<>
<SchemaField
name={configName("rows")}
schema={{
type: "number",
title: "# Rows",
description:
"The number of visible text lines for the control. If it is not specified, the default value is 2.",
}}
/>
<SchemaField
name={configName("submitOnEnter")}
schema={{
type: "boolean",
title: "Submit Form on Enter?",
description:
"If enabled, pressing Enter will submit the form. Press Shift+Enter for newlines in this mode",
}}
isRequired
/>
<SchemaField
name={configName("submitToolbar", "show")}
schema={{
type: "boolean",
title: "Include Submit Toolbar?",
description:
"Enable the submit toolbar that has a selectable icon to act as a submit button",
}}
isRequired
/>
<Collapse in={showSubmitToolbar ?? false}>
<SchemaField
name={configName("submitToolbar", "icon")}
schema={{ $ref: "https://app.pixiebrix.com/schemas/icon#" }}
label="Select Icon"
description="Select the icon that appears in the bottom right of the Submit Toolbar"
uiSchema={{
"ui:widget": "IconWidget",
}}
/>
</Collapse>
</>
);
};

export default TextAreaFields;
Original file line number Diff line number Diff line change
Expand Up @@ -177,5 +177,5 @@ describe("RichTextWidget", () => {
},
{ submissionCount: 1 },
);
}, 15_000);
}, 20_000);
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import React from "react";
import { type WidgetProps } from "@rjsf/utils";
import { type ErrorSchema, type WidgetProps } from "@rjsf/utils";
import RichTextEditor from "@/components/richTextEditor/RichTextEditor";

const RichTextWidget: React.FunctionComponent<WidgetProps> = ({
Expand All @@ -26,20 +26,35 @@ const RichTextWidget: React.FunctionComponent<WidgetProps> = ({
onBlur,
disabled,
readonly,
}) => (
<RichTextEditor
onUpdate={({ editor }) => {
onChange(editor.getHTML());
}}
onFocus={({ editor }) => {
editor.commands.focus();
onFocus(id, editor.getHTML());
}}
onBlur={({ editor }) => {
onBlur(id, editor.getHTML());
}}
editable={!(disabled || readonly)}
/>
);
options,
value,
}) => {
const { database } = options;

if (!database) {
Copy link
Contributor

@twschiller twschiller Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like using an error in the field preview as a way of showing an error the user has not provided a value. I prefer we stick with showing it as a field error annotation in the Brick Configuration pane. (I think you'd add as a check in the analysis here:

)

Was there a specific reason you chose to show it in the preview/rendered form instead?

I won't block this PR on it though to keep the ball rolling

Copy link
Collaborator Author

@mnholtz mnholtz Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a specific reason you chose to show it in the preview/rendered form instead?

I wanted to make sure at minimum that the form displayed errors at run-time due to consideration for passing in variables, but I agree that also including the error annotation in the form itself is ideal.

However - at this point, I'm more convinced that we should favor the approach of making the field optional in the next slice, as it will 1) be easy to do, 2) be more compatible with putting asset database creation behind a feature flag, and 3) reduce complexity around handling form errors.

Let's merge as-is, and I'll remove the required field limitation in favor of disabling the image feature in the next slice.

// TODO: Can't figure out how to satisfy this type without casting, but this is how it's done in the docs
// https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-widgets-fields/#raising-errors-from-within-a-custom-widget-or-field
const databaseConfigurationError = {
__errors: ["Rich text field asset database is required"],
} as ErrorSchema;
onChange(value, databaseConfigurationError);
}

return (
<RichTextEditor
onUpdate={({ editor }) => {
onChange(editor.getHTML());
}}
onFocus={({ editor }) => {
editor.commands.focus();
onFocus(id, editor.getHTML());
}}
onBlur={({ editor }) => {
onBlur(id, editor.getHTML());
}}
editable={!(disabled || readonly)}
/>
);
};

export default RichTextWidget;
Loading