diff --git a/lang/en/fieldtypes.php b/lang/en/fieldtypes.php index a78ffa23728..268ed83041a 100644 --- a/lang/en/fieldtypes.php +++ b/lang/en/fieldtypes.php @@ -105,6 +105,7 @@ 'form.config.max_items' => 'Set a maximum number of selectable forms.', 'form.config.query_scopes' => 'Choose which query scopes should be applied when retrieving selectable forms.', 'form.title' => 'Form', + 'form_instances.title' => 'Form Instances', 'grid.config.add_row' => 'Customize the label of the "Add Row" button.', 'grid.config.border' => 'Show a border and padding around fields in this group.', 'grid.config.fields' => 'Each field becomes a column in the grid table.', diff --git a/lang/en/messages.php b/lang/en/messages.php index d5e85401b62..6edc03e0a24 100644 --- a/lang/en/messages.php +++ b/lang/en/messages.php @@ -144,6 +144,8 @@ 'form_configure_mailer_instructions' => 'Leave blank to fall back to the default mailer.', 'form_configure_generate_fake_submissions_instructions' => 'Allow generating fake submissions and workflow testing from the submissions screen.', 'form_configure_store_instructions' => 'Disable to stop storing submissions. Events and email notifications will still be sent.', + 'form_configure_unique_instances_collections_instructions' => 'Entries in these collections get a Form Instances field, where this form can be added.', + 'form_configure_unique_instances_instructions' => 'Treat each entry that adds this form to its Form Instances field as its own instance. Submissions will be attached to the entry they were submitted from.', 'form_configure_title_instructions' => 'Use a call to action, such as \'Contact Us\'.', 'form_configure_close_date_instructions' => 'The form will stop accepting submissions after this date. Leave blank to never close.', 'form_configure_submission_limit_instructions' => 'The maximum number of submissions to accept. Leave blank for no limit.', @@ -152,6 +154,10 @@ 'form_configure_require_login_instructions' => 'Only allow logged in users to submit this form.', 'form_configure_require_login_message_instructions' => 'Shown when a logged out visitor tries to submit the form. Leave blank to use the default message.', 'form_closed_message' => 'This form is no longer accepting submissions.', + 'form_fieldtype_configure_instructions' => 'Edit a field to override the form\'s setting for this entry. Synced fields keep following the form.', + 'form_fieldtype_sync_confirmation' => 'Are you sure? This override will be removed and the field will follow the form\'s setting again.', + 'form_connect_unique_instances_warning' => 'Unique Instances are enabled for this form. This means that the form\'s connections may be overridden on a per-entry basis.', + 'form_fieldtype_connections_instructions' => 'Override the form\'s connections for this entry.', 'form_require_login_message' => 'You must be logged in to submit this form.', 'form_create_description' => 'Get started by creating your first form.', 'form_builder' => 'Form Builder', diff --git a/lang/en/validation.php b/lang/en/validation.php index c2d7b9d5279..fa1b00ed0e3 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -205,6 +205,9 @@ 'form_insight_duplicate' => 'Insight :insight is used more than once for field :field.', 'form_insight_not_applicable' => 'Insight :insight cannot be used for field :field.', 'form_insight_unknown' => 'Insight :insight does not exist.', + 'form_must_be_submitted_from_an_entry' => 'This form must be submitted from an entry.', + 'form_unique_instances_entry_field' => 'Cannot be enabled while you have a field named `entry`.', + 'form_unique_instances_reserved_entry_handle' => 'The `entry` handle is reserved on forms with Unique Instances enabled.', 'one_site_without_origin' => 'At least one site must not have an origin.', 'options_require_keys' => 'All options must have keys.', 'origin_cannot_be_disabled' => 'Cannot select a disabled origin.', diff --git a/resources/js/bootstrap/fieldtypes.js b/resources/js/bootstrap/fieldtypes.js index 6fa9e84bcdd..67c30cce03f 100644 --- a/resources/js/bootstrap/fieldtypes.js +++ b/resources/js/bootstrap/fieldtypes.js @@ -32,7 +32,11 @@ import Sites from '../components/globals/Sites.vue'; import Grid from '../components/fieldtypes/grid/Grid.vue'; import GridIndex from '../components/fieldtypes/grid/GridIndex.vue'; import GroupFieldtype from '../components/fieldtypes/GroupFieldtype.vue'; +import FormInstancesFieldtype from '../components/fieldtypes/FormInstancesFieldtype.vue'; +import FormInstancesIndexFieldtype from '../components/fieldtypes/FormInstancesIndexFieldtype.vue'; +import FormRelatedItem from '../components/fieldtypes/FormRelatedItem.vue'; import FormFieldsFieldtype from '../components/fieldtypes/FormFieldsFieldtype.vue'; +import FormConnectionsFieldtype from '../components/fieldtypes/FormConnectionsFieldtype.vue'; import FormHeadingFieldtype from '../components/fieldtypes/FormHeadingFieldtype.vue'; import FormParagraphFieldtype from '@/components/fieldtypes/FormParagraphFieldtype.vue'; import FormUploadFieldtype from '@/components/fieldtypes/FormUploadFieldtype.vue'; @@ -117,7 +121,11 @@ export default function registerFieldtypes(app) { app.component('grid-fieldtype', Grid); app.component('grid-fieldtype-index', GridIndex); app.component('group-fieldtype', GroupFieldtype); + app.component('form-related-item', FormRelatedItem); + app.component('form_instances-fieldtype', FormInstancesFieldtype); + app.component('form_instances-fieldtype-index', FormInstancesIndexFieldtype); app.component('form_fields-fieldtype', FormFieldsFieldtype); + app.component('form_connections-fieldtype', FormConnectionsFieldtype); app.component('form_heading-fieldtype', FormHeadingFieldtype); app.component('form_paragraph-fieldtype', FormParagraphFieldtype); app.component('form_upload-fieldtype', FormUploadFieldtype); diff --git a/resources/js/components/fieldtypes/FormConnectionsFieldtype.vue b/resources/js/components/fieldtypes/FormConnectionsFieldtype.vue new file mode 100644 index 00000000000..fa688fd62b5 --- /dev/null +++ b/resources/js/components/fieldtypes/FormConnectionsFieldtype.vue @@ -0,0 +1,123 @@ + + + diff --git a/resources/js/components/fieldtypes/FormInstancesFieldtype.vue b/resources/js/components/fieldtypes/FormInstancesFieldtype.vue new file mode 100644 index 00000000000..a9da8c44358 --- /dev/null +++ b/resources/js/components/fieldtypes/FormInstancesFieldtype.vue @@ -0,0 +1,183 @@ + + + diff --git a/resources/js/components/fieldtypes/FormInstancesIndexFieldtype.vue b/resources/js/components/fieldtypes/FormInstancesIndexFieldtype.vue new file mode 100644 index 00000000000..d2a27930234 --- /dev/null +++ b/resources/js/components/fieldtypes/FormInstancesIndexFieldtype.vue @@ -0,0 +1,22 @@ + + + diff --git a/resources/js/components/fieldtypes/FormRelatedItem.vue b/resources/js/components/fieldtypes/FormRelatedItem.vue new file mode 100644 index 00000000000..22366f3cae0 --- /dev/null +++ b/resources/js/components/fieldtypes/FormRelatedItem.vue @@ -0,0 +1,81 @@ + + + diff --git a/resources/js/components/forms/InlineSubmissionForm.vue b/resources/js/components/forms/InlineSubmissionForm.vue new file mode 100644 index 00000000000..a2c32ead806 --- /dev/null +++ b/resources/js/components/forms/InlineSubmissionForm.vue @@ -0,0 +1,43 @@ + + + diff --git a/resources/js/components/forms/SubmissionListing.vue b/resources/js/components/forms/SubmissionListing.vue index 76cbb437614..5f22b995bfc 100644 --- a/resources/js/components/forms/SubmissionListing.vue +++ b/resources/js/components/forms/SubmissionListing.vue @@ -26,7 +26,12 @@ const props = defineProps<{ sortDirection: string; columns: any[]; filters: any[]; - can: Record; + can?: Record; + viewInStack?: boolean; +}>(); + +const emit = defineEmits<{ + view: [submission: any]; }>(); const listing = useTemplateRef('listing'); @@ -45,6 +50,11 @@ const refresh = (): void => { watch(view, (view: View): void => preferences.set(`${preferencesPrefix}.view`, view)); +const viewSubmission = (event: Event, submission: any): void => { + if (props.viewInStack) event.preventDefault(); + emit('view', submission); +}; + defineExpose({ parameters, refresh }); @@ -59,7 +69,8 @@ defineExpose({ parameters, refresh }); :sort-direction="sortDirection" :preferences-prefix="preferencesPrefix" :filters="filters" - push-query + :allow-presets="!viewInStack" + :push-query="!viewInStack" > diff --git a/resources/js/components/forms/SubmissionPublishForm.vue b/resources/js/components/forms/SubmissionPublishForm.vue new file mode 100644 index 00000000000..e6a60074f57 --- /dev/null +++ b/resources/js/components/forms/SubmissionPublishForm.vue @@ -0,0 +1,66 @@ + + + diff --git a/resources/js/components/forms/SubmissionsStack.vue b/resources/js/components/forms/SubmissionsStack.vue new file mode 100644 index 00000000000..124d1034eb7 --- /dev/null +++ b/resources/js/components/forms/SubmissionsStack.vue @@ -0,0 +1,40 @@ + + + diff --git a/resources/js/components/forms/connections/ConnectionEditor.vue b/resources/js/components/forms/connections/ConnectionEditor.vue new file mode 100644 index 00000000000..95e9a5dc824 --- /dev/null +++ b/resources/js/components/forms/connections/ConnectionEditor.vue @@ -0,0 +1,21 @@ + + + diff --git a/resources/js/components/forms/connections/ConnectorsListing.vue b/resources/js/components/forms/connections/ConnectorsListing.vue new file mode 100644 index 00000000000..1c46e829baf --- /dev/null +++ b/resources/js/components/forms/connections/ConnectorsListing.vue @@ -0,0 +1,101 @@ + + + + + + + diff --git a/resources/js/pages/forms/connect/Edit.vue b/resources/js/pages/forms/connect/Edit.vue index f4338df1f20..6ebc07906c3 100644 --- a/resources/js/pages/forms/connect/Edit.vue +++ b/resources/js/pages/forms/connect/Edit.vue @@ -6,8 +6,9 @@ import Layout from '@/pages/layout/Layout.vue'; import PanelLayout from '@/pages/layout/PanelLayout.vue'; import FormsLayout from '../Layout.vue'; import Head from '@/pages/layout/Head.vue'; -import { Badge, Button, Card, Header, Heading, Icon, Panel, PanelHeader } from '@ui'; +import { Alert, Badge, Button, Card, Header, Heading, Icon, Panel, PanelHeader } from '@ui'; import FormStatusIndicator from '@/components/forms/FormStatusIndicator.vue'; +import ConnectionEditor from '@/components/forms/connections/ConnectionEditor.vue'; import { Link } from '@inertiajs/vue3'; defineOptions({ layout: [Layout, PanelLayout, FormsLayout] }); @@ -20,6 +21,7 @@ const props = defineProps({ action: String, isConfigured: Boolean, suggestableFields: Array, + uniqueInstancesEnabled: Boolean, }); const errors = ref({}); @@ -78,13 +80,20 @@ onUnmounted(() => {
-
+
+ +
@@ -114,13 +123,7 @@ onUnmounted(() => {
- +
diff --git a/resources/js/pages/forms/connect/Index.vue b/resources/js/pages/forms/connect/Index.vue index fd35931e054..3a7758a7695 100644 --- a/resources/js/pages/forms/connect/Index.vue +++ b/resources/js/pages/forms/connect/Index.vue @@ -3,34 +3,14 @@ import Layout from '@/pages/layout/Layout.vue'; import PanelLayout from '@/pages/layout/PanelLayout.vue'; import FormsLayout from '../Layout.vue'; import Head from '@/pages/layout/Head.vue'; -import { - Badge, - Button, - Card, - DocsCallout, - Header, - Heading, - Icon, - Panel, - PanelHeader, - ToggleGroup, - ToggleItem, - Listing, - ListingTable, -} from '@ui'; +import { Alert, Button, Card, DocsCallout, Header, Heading, Icon, Panel, PanelHeader, ToggleGroup, ToggleItem } from '@ui'; import FormStatusIndicator from '@/components/forms/FormStatusIndicator.vue'; -import { Link } from '@inertiajs/vue3'; +import ConnectorsListing, { View } from '@/components/forms/connections/ConnectorsListing.vue'; import { ref, watch } from 'vue'; -import { __ } from '@/bootstrap/globals'; import { preferences } from '@api'; defineOptions({ layout: [Layout, PanelLayout, FormsLayout] }); -enum View { - Grid = 'grid', - List = 'list', -} - type Connector = { handle: string; title: string; @@ -44,24 +24,19 @@ type Connector = { defineProps<{ form: Object, connectors: Connector[], + uniqueInstancesEnabled: boolean, }>(); const view = ref(preferences.get('forms.connect.view', View.Grid)); -watch(view, (view: View) => preferences.set('forms.connect.view', view)); +watch(view, (view) => preferences.set('forms.connect.view', view)); - -