-
Notifications
You must be signed in to change notification settings - Fork 6
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
Related pubs in form #910
Related pubs in form #910
Conversation
0648f6c
to
3751834
Compare
@@ -175,6 +176,18 @@ export const FormElement = ({ | |||
); | |||
} | |||
|
|||
if (element.isRelation) { | |||
const valueComponent = input; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a small hope this would Just Work, but it doesn't :) We want to render an "wrapper" component and an "inner" component
The 'wrapper' component is the 'Pub Relations' box, and the 'inner' component is the popover 'Role' in this screenshot. Without taking any of the changes in this PR into account, FormElement
would return the contents of 'Role', which is what we want, just in the popover instead, so I stashed that as valueComponent
here. Then render the 'wrapper' as RelatedPubsElement
.
This doesn't actually work though because the value
of the form is going to be an array now—something like {'croccroc:author': [ {value: 'admin', relatedPubId: 'xxx'} ]}
so currently we end up rendering
.
The only way I can think of to get this to work is to render a separate component inside RelatedPubsElement
based on the CoreSchemaType
that is a lot like FormElement.tsx
but doesn't make all the same assumptions, and might not use i.e. TextInputElement.tsx
since that tries to get the field value directly when really we need to index into the array.
maybe I'm missing a simpler way to do this though??
7a509bb
to
1995b69
Compare
@@ -95,49 +96,12 @@ const elementPanelTitles: Record<PanelState["state"], string> = { | |||
editingButton: "Edit Submission Button", | |||
}; | |||
|
|||
const PanelHeader = ({ state }: { state: PanelState["state"] }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved all the side panel stuff to another component so I could reuse it in the form fill page. I think this would be better off using a drawer or maybe even the new side bar component from shadcn, but for now I reused what we had to keep things consistent
@@ -1,48 +1,24 @@ | |||
import { defaultComponent } from "schemas"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,172 @@ | |||
import { defaultComponent } from "schemas"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty much directly pulled from FormElement.tsx
(pv) => pv.relatedPubId | ||
); | ||
|
||
if (pubValuesWithRelations.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this might result in an inconsistent return—should I update replacePubRelationsBySlug
to also return something like what the .insertInto("pub_values")
below does and then combine them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think that if this works then just leave it, i'm currently rewriting a bunch of this anyway!
body = error[0]?.message; | ||
const firstErrorIndex = error.findIndex((e) => !!e); | ||
const firstError = error[firstErrorIndex]; | ||
body = firstError?.message ?? `Error with value at index ${firstErrorIndex}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
demoComponent: ({ element }) => ( | ||
<Input | ||
placeholder={ | ||
element.schemaName === CoreSchemaType.String ? "For short text" : "For numbers" | ||
} | ||
type={element.schemaName === CoreSchemaType.String ? "text" : "number"} | ||
/> | ||
), | ||
demoComponent: ({ element }) => { | ||
const isNumber = element.schemaName === CoreSchemaType.Number; | ||
return ( | ||
<Input | ||
placeholder={isNumber ? "For numbers" : "For short text"} | ||
type={isNumber ? "number" : "text"} | ||
/> | ||
); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
email and url were showing up as number input components in the demo portion
This is ready for review but MemberSelect doesn't work—if chosen, there'll be an error that I haven't put a lot of time into investigating yet, but I suspect it may be thorny! So I'm thinking I'll split that out into a separate chunk of work. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really really nice, great work! I left some suggestions about slightly nicer error displays for the values and using pubtitles in a bunch of places, if you incorporate those im happy to merge it!
return ( | ||
<div className="flex items-center gap-2"> | ||
<span>{row.original.title || row.original.id}</span> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use the PubTitle here, rather than the id as fallback. Is somewhat clearer, and I don't think we want to show external users these ids
</div> | |
<span>{getPubTitle(row.original)}</span> |
|
||
import type { GetPubsResult } from "~/lib/server"; | ||
import { PanelHeader, SidePanel } from "~/app/components/SidePanel"; | ||
import { DataTable } from "../DataTable/v2/DataTable"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see below
import { DataTable } from "../DataTable/v2/DataTable"; | |
import { getPubTitle } from "~/lib/pubs"; |
const configLabel = "label" in element.config ? element.config.label : undefined; | ||
const label = configLabel || element.label || slug; | ||
|
||
const { watch } = useFormContext(); | ||
const [isPopoverOpen, setPopoverIsOpen] = useState(false); | ||
const value = watch(slug); | ||
const showValue = value != null && value !== "" && !isPopoverOpen; | ||
|
||
if (element.component === null) { | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this way we have slightly better error reporting
const configLabel = "label" in element.config ? element.config.label : undefined; | |
const label = configLabel || element.label || slug; | |
const { watch } = useFormContext(); | |
const [isPopoverOpen, setPopoverIsOpen] = useState(false); | |
const value = watch(slug); | |
const showValue = value != null && value !== "" && !isPopoverOpen; | |
if (element.component === null) { | |
return null; | |
}: PubFieldFormElementProps & { slug: RelatedPubValueSlug }) => { | |
const configLabel = "label" in element.config ? element.config.label : undefined; | |
const label = configLabel || element.label || slug; | |
const { watch, formState } = useFormContext<FormValue>(); | |
const [isPopoverOpen, setPopoverIsOpen] = useState(false); | |
const value = watch(slug); | |
const showValue = value != null && value !== "" && !isPopoverOpen; | |
const [baseSlug, index] = slug.split("."); | |
const valueError = formState.errors[baseSlug]?.[parseInt(index)]?.value; | |
if (element.component === null) { | |
return null; |
import { MultiBlock } from "ui/multiblock"; | ||
import { Popover, PopoverContent, PopoverTrigger } from "ui/popover"; | ||
|
||
import type { PubFieldFormElementProps } from "../PubFieldFormElement"; | ||
import type { ElementProps } from "../types"; | ||
import type { GetPubsResult } from "~/lib/server"; | ||
import { AddRelatedPubsPanel } from "~/app/components/forms/AddRelatedPubsPanel"; | ||
import { useContextEditorContext } from "../../ContextEditor/ContextEditorContext"; | ||
import { useFormElementToggleContext } from "../FormElementToggleContext"; | ||
import { PubFieldFormElement } from "../PubFieldFormElement"; | ||
|
||
const RelatedPubBlock = ({ | ||
pub, | ||
onRemove, | ||
valueComponentProps, | ||
slug, | ||
}: { | ||
pub: Pick<GetPubsResult[number], "title" | "id">; | ||
onRemove: () => void; | ||
valueComponentProps: PubFieldFormElementProps; | ||
slug: string; | ||
}) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
necessities for the error reporting down below
import { MultiBlock } from "ui/multiblock"; | |
import { Popover, PopoverContent, PopoverTrigger } from "ui/popover"; | |
import type { PubFieldFormElementProps } from "../PubFieldFormElement"; | |
import type { ElementProps } from "../types"; | |
import type { GetPubsResult } from "~/lib/server"; | |
import { AddRelatedPubsPanel } from "~/app/components/forms/AddRelatedPubsPanel"; | |
import { useContextEditorContext } from "../../ContextEditor/ContextEditorContext"; | |
import { useFormElementToggleContext } from "../FormElementToggleContext"; | |
import { PubFieldFormElement } from "../PubFieldFormElement"; | |
const RelatedPubBlock = ({ | |
pub, | |
onRemove, | |
valueComponentProps, | |
slug, | |
}: { | |
pub: Pick<GetPubsResult[number], "title" | "id">; | |
onRemove: () => void; | |
valueComponentProps: PubFieldFormElementProps; | |
slug: string; | |
}) => { | |
import { Plus, Trash, TriangleAlert } from "ui/icon"; | |
import { MultiBlock } from "ui/multiblock"; | |
import { Popover, PopoverContent, PopoverTrigger } from "ui/popover"; | |
import { cn } from "utils"; | |
import type { PubFieldFormElementProps } from "../PubFieldFormElement"; | |
import type { ElementProps } from "../types"; | |
import type { GetPubsResult } from "~/lib/server"; | |
import { AddRelatedPubsPanel } from "~/app/components/forms/AddRelatedPubsPanel"; | |
import { getPubTitle } from "~/lib/pubs"; | |
import { useContextEditorContext } from "../../ContextEditor/ContextEditorContext"; | |
import { useFormElementToggleContext } from "../FormElementToggleContext"; | |
import { PubFieldFormElement } from "../PubFieldFormElement"; | |
type RelatedPubValueSlug = `${string}.${number}.value`; | |
const RelatedPubBlock = ({ | |
pub, | |
onRemove, | |
valueComponentProps, | |
slug, | |
}: { | |
pub: Pick<GetPubsResult[number], "title" | "id" | "createdAt" | "pubType" | "values">; | |
onRemove: () => void; | |
valueComponentProps: PubFieldFormElementProps; | |
slug: RelatedPubValueSlug; |
remove(index); | ||
}; | ||
const innerSlug = `${slug}.${index}.value`; | ||
return ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final part for the error reporting above
return ( | |
const innerSlug = | |
`${slug}.${index}.value` as const; |
<div className="flex items-center justify-between rounded border border-l-[12px] border-l-emerald-100 p-3"> | ||
<div className="flex flex-col items-start gap-1 text-sm"> | ||
<span className="font-semibold">{title || id}</span> | ||
<ConfigureRelatedValue {...valueComponentProps} slug={slug} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use the pubtitle here as well
<ConfigureRelatedValue {...valueComponentProps} slug={slug} /> | |
<span className="font-semibold">{getPubTitle(pub)}</span> |
(pv) => pv.relatedPubId | ||
); | ||
|
||
if (pubValuesWithRelations.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think that if this works then just leave it, i'm currently rewriting a bunch of this anyway!
Co-authored-by: Thomas F. K. Jorna <[email protected]>
Co-authored-by: Thomas F. K. Jorna <[email protected]>
Issue(s) Resolved
Most of #791
High-level Explanation of PR
Adds related pub configuration to the external fill form.
Test Plan
And also configure the 'value' for the relationship


This should work for more complex fields like StringArrays too and include validation on the 'value' field


Save the form. Visiting the form again should load your values (you can visit again by visiting the url
http://localhost:3000/c/{communitySlug}/public/forms/{formSlug}/fill?pubId={pubId})
You can also visit the pub page for this created pub and see your related pubs

Screenshots (if applicable)
Notes
Known issues: