diff --git a/demo/app/Models/Category.php b/demo/app/Models/Category.php index 7eff73631..757a5255a 100644 --- a/demo/app/Models/Category.php +++ b/demo/app/Models/Category.php @@ -5,12 +5,17 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Spatie\Translatable\HasTranslations; class Category extends Model { use HasFactory; + use HasTranslations; protected $guarded = []; + public array $translatable = [ + 'description', + ]; public function posts(): BelongsToMany { diff --git a/demo/app/Sharp/Authors/Commands/InviteUserCommand.php b/demo/app/Sharp/Authors/Commands/InviteUserCommand.php index 00f7cbd9c..f47a33aa4 100644 --- a/demo/app/Sharp/Authors/Commands/InviteUserCommand.php +++ b/demo/app/Sharp/Authors/Commands/InviteUserCommand.php @@ -16,7 +16,9 @@ public function label(): ?string public function buildCommandConfig(): void { $this->configureFormModalTitle('Invite a new user as author') - ->configureFormModalDescription('Provide the email address of the new author, and an invitation will be sent to him (not true, we won’t send anything).'); + ->configureFormModalDescription('Provide the email address of the new author, and an invitation will be sent to him (not true, we won’t send anything).') + ->configureFormModalButtonLabel('Send invitation') + ->configureFormModalSubmitAndReopenButton('Send & send another'); } public function buildFormFields(FieldsContainer $formFields): void diff --git a/demo/app/Sharp/Categories/CategoryForm.php b/demo/app/Sharp/Categories/CategoryForm.php index 6a8363335..2328707a0 100644 --- a/demo/app/Sharp/Categories/CategoryForm.php +++ b/demo/app/Sharp/Categories/CategoryForm.php @@ -4,6 +4,7 @@ use App\Models\Category; use Code16\Sharp\Form\Eloquent\WithSharpFormEloquentUpdater; +use Code16\Sharp\Form\Fields\SharpFormTextareaField; use Code16\Sharp\Form\Fields\SharpFormTextField; use Code16\Sharp\Form\Layout\FormLayout; use Code16\Sharp\Form\Layout\FormLayoutColumn; @@ -21,13 +22,21 @@ public function buildFormFields(FieldsContainer $formFields): void SharpFormTextField::make('name') ->setLabel('Name') ->setMaxLength(150), + ) + ->addField( + SharpFormTextareaField::make('description') + ->setLabel('Description') + ->setRowCount(4) + ->setLocalized() + ->setMaxLength(500), ); } public function buildFormLayout(FormLayout $formLayout): void { $formLayout->addColumn(6, function (FormLayoutColumn $column) { - $column->withField('name'); + $column->withField('name') + ->withField('description'); }); } @@ -49,8 +58,9 @@ public function find($id): array public function update($id, array $data) { $this->validate( - $data, - ['name' => ['required', 'string', 'max:150']] + $data, [ + 'name' => ['required', 'string', 'max:150'], + ] ); $category = $id diff --git a/demo/app/Sharp/Categories/CategoryList.php b/demo/app/Sharp/Categories/CategoryList.php index 0ba1a38db..6a51f0904 100644 --- a/demo/app/Sharp/Categories/CategoryList.php +++ b/demo/app/Sharp/Categories/CategoryList.php @@ -28,7 +28,8 @@ protected function buildList(EntityListFieldsContainer $fields): void public function buildListConfig(): void { - $this->configureReorderable(new SimpleEloquentReorderHandler(Category::class)); + $this->configureReorderable(new SimpleEloquentReorderHandler(Category::class)) + ->configureQuickCreationForm(['name']); } protected function getEntityCommands(): ?array diff --git a/demo/app/Sharp/Categories/CategoryShow.php b/demo/app/Sharp/Categories/CategoryShow.php index 6726f4a43..e60b664bc 100644 --- a/demo/app/Sharp/Categories/CategoryShow.php +++ b/demo/app/Sharp/Categories/CategoryShow.php @@ -27,15 +27,20 @@ public function find(mixed $id): array protected function buildShowFields(FieldsContainer $showFields): void { $showFields - ->addField(SharpShowTextField::make('name')->setLabel('Name')) + ->addField( + SharpShowTextField::make('name') + ->setLabel('Name')) + ->addField( + SharpShowTextField::make('description') + ->setLocalized() + ->setLabel('Description') + ) ->addField( SharpShowEntityListField::make('posts') ->setLabel('Related posts') ->showCreateButton(false) ->showCount() - ->hideFilterWithValue(CategoryFilter::class, function ($instanceId) { - return $instanceId; - }), + ->hideFilterWithValue(CategoryFilter::class, fn ($instanceId) => $instanceId) ); } @@ -45,7 +50,8 @@ protected function buildShowLayout(ShowLayout $showLayout): void ->addSection('', function (ShowLayoutSection $section) { $section ->addColumn(6, function (ShowLayoutColumn $column) { - $column->withField('name'); + $column->withField('name') + ->withField('description'); }); }) ->addEntityListSection('posts', collapsable: true); @@ -55,4 +61,9 @@ public function delete($id): void { Category::findOrFail($id)->delete(); } + + public function getDataLocalizations(): array + { + return ['fr', 'en']; + } } diff --git a/demo/app/Sharp/Posts/Blocks/PostBlockList.php b/demo/app/Sharp/Posts/Blocks/PostBlockList.php index 1b0653661..041dbc6a7 100644 --- a/demo/app/Sharp/Posts/Blocks/PostBlockList.php +++ b/demo/app/Sharp/Posts/Blocks/PostBlockList.php @@ -30,7 +30,8 @@ protected function buildList(EntityListFieldsContainer $fields): void public function buildListConfig(): void { $this->configureMultiformAttribute('type') - ->configureReorderable(new SimpleEloquentReorderHandler(PostBlock::class)); + ->configureReorderable(new SimpleEloquentReorderHandler(PostBlock::class)) + ->configureQuickCreationForm(); } protected function getFilters(): ?array diff --git a/demo/database/factories/CategoryFactory.php b/demo/database/factories/CategoryFactory.php index 2f3c70616..6dca4af16 100644 --- a/demo/database/factories/CategoryFactory.php +++ b/demo/database/factories/CategoryFactory.php @@ -13,6 +13,7 @@ public function definition() { return [ 'name' => $this->faker->unique()->randomElement(static::$categoryNames), + 'description' => $this->faker->paragraph(), ]; } } diff --git a/demo/database/migrations/2022_03_02_145244_create_categories_table.php b/demo/database/migrations/2022_03_02_145244_create_categories_table.php index 1ca802365..b1681a87c 100644 --- a/demo/database/migrations/2022_03_02_145244_create_categories_table.php +++ b/demo/database/migrations/2022_03_02_145244_create_categories_table.php @@ -11,6 +11,7 @@ public function up() Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); + $table->text('description')->nullable(); $table->unsignedSmallInteger('order')->default(100); $table->timestamps(); }); diff --git a/docs/.vitepress/sidebar.ts b/docs/.vitepress/sidebar.ts index 291a44b56..f9ba9d49c 100644 --- a/docs/.vitepress/sidebar.ts +++ b/docs/.vitepress/sidebar.ts @@ -28,6 +28,7 @@ export function sidebar(): DefaultTheme.SidebarItem[] { { text: 'Create a Form', link: '/guide/building-form.md' }, { text: 'Multi-Forms', link: '/guide/multiforms.md' }, { text: 'Using Single Form for unique resources', link: '/guide/single-form.md' }, + { text: 'Quick creation form', link: '/guide/quick-creation-form.md' }, { text: 'Write an Embed for the Editor field', link: '/guide/form-editor-embeds.md' }, { text: 'Text', link: '/guide/form-fields/text.md' }, { text: 'Textarea', link: '/guide/form-fields/textarea.md' }, diff --git a/docs/guide/building-entity-list.md b/docs/guide/building-entity-list.md index d4e808d03..050dd2167 100644 --- a/docs/guide/building-entity-list.md +++ b/docs/guide/building-entity-list.md @@ -199,6 +199,8 @@ Here is the full list of available methods: - `configurePrimaryEntityCommand(string $commandKeyOrClassName)`: define an instance command as "primary", by passing its key or full cass name. The command should be declared for this Entity List ([see related doc](commands.md)). +- `configureQuickCreationForm(?array $fields = null)`: show the creation form in a modal instead of a full page ([see detailed doc](quick-creation-form.md)) + - `configureDelete(bool $hide = false, ?string $onfirmationText = null)`: the first argument is to show / hide the delete command on each instance (shown by default); this is only useful to hide the link if you want to only display the delete action in the Show Page (if you have defined one), this is NOT to be used for authorization purpose (see [dedicated documentation on this topic](entity-authorizations.md)). The second argument is the message to display in the confirmation dialog (a sensible default will be used). - `configureCreateButtonLabel(string $label)` to set a custom "New..." button label. diff --git a/docs/guide/commands.md b/docs/guide/commands.md index 585079242..f62773123 100644 --- a/docs/guide/commands.md +++ b/docs/guide/commands.md @@ -45,9 +45,12 @@ The example above is an "entity" case, which is reserved to Entity Lists: Comman To create an instance Command (relative to a specific instance, which can be placed on each Entity List row, or in a Show Page), the Command class must extend `Code16\Sharp\EntityList\Commands\InstanceCommand`. The execute method signature is a bit different: ```php -public function execute($instanceId, array $params = []): array +class PromoteToAdminCommand extends InstanceCommand { - // [...] + public function execute($instanceId, array $params = []): array + { + // ... + } } ``` @@ -60,7 +63,7 @@ The second parameter in the `execute()` function is an array named `$data`, whic ```php class SendInvoiceToCustomerCommand extends InstanceCommand { - // [...] + // ... function buildFormFields(FieldsContainer $formFields): void { @@ -84,7 +87,7 @@ Once this method has been declared, a form will be prompted to the user in a mod ```php class SendInvoiceToCustomerCommand extends InstanceCommand { - // [...] + // ... public function execute($instanceId, array $data = []): array { @@ -101,7 +104,7 @@ class SendInvoiceToCustomerCommand extends InstanceCommand ``` ::: tip -Notice that validation can be extracted to a dedicated `rules()` method instead. +Validation can be extracted to a dedicated `rules()` method instead. ::: #### Initializing form data @@ -122,7 +125,7 @@ For an Instance command, add the `$instanceId` as a parameter: ```php protected function initialData($instanceId): array { - // [...] + // ... } ``` @@ -162,10 +165,11 @@ Here is the full list of available methods: - `configureFormModalTitle(string $formModalTitle)`: if the Command has a Form, the title of the modal will be its label, or `$formModalTitle` if defined - `configureFormModalButtonLabel(string $formModalButtonLabel)`: if the Command has a Form, the label of the OK button will be `$formModalButtonLabel` - `configurePageAlert(string $template, string $alertLevel = null, string $fieldKey = null, bool $declareTemplateAsPath = false)`: display a dynamic message above the Form; [see detailed doc](page-alerts.md) +- `configureFormModalSubmitAndReopenButton(?string $label = null)`: only useful to Commands with forms; if set, an additional button will be displayed to allow the user to submit the form and immediately reopen the Command; the label of the button will be `$label` if defined. ### Command return types -Finally, let's review the return possibilities: after a Command has been executed, the code must return something to tell to the front what to do next. There are six of them: +Finally, let's review the return possibilities: after a Command has been executed, the code must return something to tell to the front what to do next. There are height of them: - `return $this->info('some text')`: displays the entered text in a modal. - `return $this->reload()`: reload the current page (with context). @@ -181,16 +185,14 @@ Finally, let's review the return possibilities: after a Command has been execute ```php class OrderList extends SharpEntityList { - // [...] - function getListData() + // ... + function getListData(): array|Arrayble { - $orders = Order::query(); - - if($params->specificIds()) { - $orders->whereIn('id', $this->queryParams->specificIds()); - } - - return $this->transform($orders->get()); + return Order::query() + ->when($this->queryParams->specificIds(), function (Builder $query, $ids) { + $query->whereIn('id', $ids); + }) + ->transform($orders->get()); } } ``` @@ -202,7 +204,7 @@ In the same fashion as for a Form, you can display notifications after a Command ```php public function execute($instanceId, array $data= []): array { - // [...] + // ... $this->notify('This is done.') ->setDetail('As you asked.') @@ -226,7 +228,7 @@ Once the Command class is written, we must add it to the Entity List or Show Pag ```php class OrderList extends SharpEntityList { - // [...] + // ... function getInstanceCommands(): ?array { return [ @@ -248,7 +250,7 @@ or to the Dashboard: ```php class SalesDashboard extends SharpDashboard { - // [...] + // ... function getDashboardCommands(): ?array { @@ -305,7 +307,7 @@ An Entity List can declare one (and only one) of its entity Commands as "primary ```php class UserList extends SharpEntityList { - // [...] + // ... function buildListConfig(): void { @@ -339,7 +341,7 @@ To achieve this, you must choose a unique key and attach it to the layout sectio ```php class PostShow extends SharpShow { - // [...] + // ... protected function buildShowLayout(ShowLayout $showLayout): void { @@ -386,7 +388,7 @@ As seen before, Entity Commands are executed on multiple instances: either all o ```php class MyBulkCommand extends EntityCommand { - // [...] + // ... public function buildCommandConfig(): void { @@ -406,7 +408,7 @@ Use the `$this->selectedIds()` method to retrieve the list of selected instances ```php class MyBulkCommand extends EntityCommand { - // [...] + // ... public function execute(array $data = []): array { diff --git a/docs/guide/quick-creation-form.md b/docs/guide/quick-creation-form.md new file mode 100644 index 000000000..e94d3fbd2 --- /dev/null +++ b/docs/guide/quick-creation-form.md @@ -0,0 +1,41 @@ +# Quick creation form + +Sometimes you may want to allow the creation of a new instance directly from the list page, without having to navigate to a dedicated creation page. It's especially useful when the create form does not require a lot of fields, to keep the user in the list context — and since Sharp will display a "submit and reopen" button in the modal, the user can quickly create multiple instances. + +## Prerequisites + +This feature will only work if a Form is defined for the entity (since Sharp will entirely rely on it). + +## Configuration + +The configuration is done in the Entity List: + +```php +class MyList extends SharpEntityList +{ + public function buildListConfig(): void + { + $this->configureQuickCreationForm(); + } + // ... +} +``` + +With this, when the user clicks on the "New..." button, a modal will open with the form fields defined in the Form. One common practice is to limit the fields to the strict minimum: this can be achieved by passing an array of field keys to the `configureQuickCreationForm` method: + +```php +class MyList extends SharpEntityList +{ + public function buildListConfig(): void + { + $this->configureQuickCreationForm(['name', 'price']); + } + // ... +} +``` + +Of course, ensure that these fields are defined in the Form and that all the required fields are present. + +## Redirect to the Show Page + +When the Form is configured with `configureDisplayShowPageAfterCreation()`, and if the user does not choose to stay in creation (with the "submit and reopen" button), Sharp will redirect to the Show Page after the creation. \ No newline at end of file diff --git a/resources/js/Pages/EntityList/EntityList.vue b/resources/js/Pages/EntityList/EntityList.vue index 6ce160229..f90ebcec0 100644 --- a/resources/js/Pages/EntityList/EntityList.vue +++ b/resources/js/Pages/EntityList/EntityList.vue @@ -26,8 +26,9 @@ const entityList: Ref = ref(new EntityList(props.entityList, entityKey)); const filters = useFilters(entityList.value.config.filters, entityList.value.filterValues); const commands = useCommands('entityList', { - refresh: (data) => { - entityList.value = entityList.value.withRefreshedItems(data.items) + refresh: (data, { formModal }) => { + entityList.value = entityList.value.withRefreshedItems(data.items); + formModal.shouldReopen && formModal.reopen(); }, }); diff --git a/resources/js/commands/CommandManager.ts b/resources/js/commands/CommandManager.ts index a491e9585..346eb7705 100644 --- a/resources/js/commands/CommandManager.ts +++ b/resources/js/commands/CommandManager.ts @@ -6,7 +6,13 @@ import { AxiosResponse } from "axios"; import { reactive } from "vue"; import { __ } from "@/utils/i18n"; import { router } from "@inertiajs/vue3"; -import { CommandResponseHandlers, CommandEndpoints, GetFormQuery, CommandContainer } from "./types"; +import { + CommandResponseHandlers, + CommandEndpoints, + GetFormQuery, + CommandContainer, + CommandFormExtraData +} from "./types"; import { Form } from "@/form/Form"; @@ -20,6 +26,7 @@ export class CommandManager { currentCommandFormLoading?: boolean, currentCommandResponse?: CommandResponseData, currentCommandEndpoints?: CommandEndpoints, + currentCommandShouldReopen?: boolean, }; constructor(commandContainer: CommandContainer, commandResponseHandlers?: Partial) { @@ -28,16 +35,22 @@ export class CommandManager { ...this.defaultCommandResponseHandlers, ...commandResponseHandlers, } + this.maybeReopenFromSessionStorage(); } get defaultCommandResponseHandlers(): CommandResponseHandlers { return { - info: async ({ message }) => { + info: async ({ message }, { formModal }) => { await showAlert(message, { title: __('sharp::modals.command.info.title'), }); + formModal.shouldReopen && formModal.reloadAndReopen(); }, - link: ({ link }) => { + link: ({ link }, { formModal }) => { + if(formModal.shouldReopen) { + formModal.reloadAndReopen(); + return; + } const url = new URL(link); if(url.origin === location.origin) { router.visit(url.pathname + url.search); @@ -45,20 +58,22 @@ export class CommandManager { location.href = link; } }, - reload: () => { + reload: (data, { formModal }) => { return new Promise((resolve) => router.visit(location.href, { preserveState: false, - preserveScroll: false, + preserveScroll: true, + onStart: () => formModal.shouldReopen && formModal.queueReopen(), onFinish: () => resolve(), }) ); }, - refresh: () => { + refresh: (data, { formModal }) => { return new Promise((resolve) => router.visit(location.href, { preserveState: false, - preserveScroll: false, + preserveScroll: true, + onStart: () => formModal.shouldReopen && formModal.queueReopen(), onFinish: () => resolve(), }) ); @@ -69,7 +84,11 @@ export class CommandManager { command_step: data.step, }); }, - view: (data) => { + view: (data, { formModal }) => { + if(formModal.shouldReopen) { + formModal.reloadAndReopen(); + return; + } this.state.currentCommandResponse = data; }, }; @@ -80,7 +99,7 @@ export class CommandManager { this.state.currentCommand = command; this.state.currentCommandEndpoints = endpoints; - if(command.has_form) { + if(command.hasForm) { this.state.currentCommandForm = await this.getForm(); return; } @@ -110,6 +129,7 @@ export class CommandManager { this.state.currentCommandFormLoading = false; this.state.currentCommandResponse = null; this.state.currentCommandEndpoints = null; + this.state.currentCommandShouldReopen = false; } async handleCommandApiResponse(response: AxiosResponse): Promise { @@ -118,6 +138,10 @@ export class CommandManager { link.download = (response.headers['content-disposition'] as string)?.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)?.[1] ?? 'download'; link.href = URL.createObjectURL(response.data); link.click(); + if(this.state.currentCommandShouldReopen) { + await this.reopenCurrentCommand(); + return; + } this.finish(); return; } @@ -132,8 +156,25 @@ export class CommandManager { await this.handleCommandResponse(data); } + getCommandResponseHandlerContext() { + return { + formModal: { + shouldReopen: this.state.currentCommandShouldReopen, + queueReopen: () => { + this.queueReopenInSessionStorage(); + }, + reopen: () => { + this.reopenCurrentCommand(); + }, + reloadAndReopen: () => { + this.handleCommandResponse({ action: 'reload' }); + }, + }, + } + } + handleCommandResponse(data: CommandResponseData): void | Promise { - return this.commandResponseHandlers[data.action]?.(data as any); + return this.commandResponseHandlers[data.action]?.(data as any, this.getCommandResponseHandlerContext()); } getForm(query?: GetFormQuery): Promise
{ @@ -158,8 +199,9 @@ export class CommandManager { }); } - async postForm(data: CommandFormData['data']) { + async postForm(data: CommandFormData['data'] & CommandFormExtraData) { this.state.currentCommandFormLoading = true; + this.state.currentCommandShouldReopen = data._shouldReopen ?? false; try { const response = await api.post(this.state.currentCommandEndpoints.postCommand, { @@ -177,4 +219,33 @@ export class CommandManager { this.state.currentCommandFormLoading = false; } } + + async reopenCurrentCommand() { + this.state.currentCommandShouldReopen = false; + this.state.currentCommandForm = await this.getForm(); + } + + queueReopenInSessionStorage() { + if(this.state.currentCommandShouldReopen) { + const data = { + command: this.state.currentCommand, + commandEndpoints: this.state.currentCommandEndpoints, + url: location.href, + }; + sessionStorage.setItem('reopen-command', JSON.stringify(data)); + return data; + } + } + + maybeReopenFromSessionStorage() { + const reopenData: ReturnType = JSON.parse( + sessionStorage.getItem('reopen-command') ?? 'null' + ); + if(reopenData) { + if(location.href === reopenData.url) { + this.send(reopenData.command, reopenData.commandEndpoints); + } + sessionStorage.removeItem('reopen-command'); + } + } } diff --git a/resources/js/commands/components/CommandFormModal.vue b/resources/js/commands/components/CommandFormModal.vue index 0773b1154..e386b465d 100644 --- a/resources/js/commands/components/CommandFormModal.vue +++ b/resources/js/commands/components/CommandFormModal.vue @@ -12,8 +12,8 @@ DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; - import { Loader2 } from "lucide-vue-next"; import { __ } from "@/utils/i18n"; + import { CommandFormExtraData } from "@/commands/types"; const props = defineProps<{ commands: CommandManager, @@ -61,6 +61,14 @@ {{ __('sharp::modals.cancel_button') }} + diff --git a/resources/js/commands/types.ts b/resources/js/commands/types.ts index 70ca56ec2..13c2211af 100644 --- a/resources/js/commands/types.ts +++ b/resources/js/commands/types.ts @@ -1,9 +1,17 @@ import { CommandResponseData } from "@/types"; +import { CommandManager } from "@/commands/CommandManager"; export type CommandContainer = 'dashboard' | 'entityList' | 'show'; export type CommandResponseHandlers = { - [Action in CommandResponseData['action']]: (data: Extract) => void | Promise + [Action in CommandResponseData['action']]: ( + data: Extract, + context: ReturnType + ) => void | Promise +} + +export type CommandFormExtraData = { + _shouldReopen?: boolean } export type GetFormQuery = { diff --git a/resources/js/entity-list/EntityList.ts b/resources/js/entity-list/EntityList.ts index 9dfcab9f0..c4768c364 100644 --- a/resources/js/entity-list/EntityList.ts +++ b/resources/js/entity-list/EntityList.ts @@ -74,7 +74,7 @@ export class EntityList implements EntityListData { } get canSelect() { - return this.allowedEntityCommands.flat().some(command => command.instance_selection); + return this.allowedEntityCommands.flat().some(command => command.instanceSelection); } get canReorder() { @@ -95,7 +95,7 @@ export class EntityList implements EntityListData { return this.allowedEntityCommands.map(commandGroup => commandGroup.filter(command => { if(selecting) { - return !!command.instance_selection; + return !!command.instanceSelection; } return !command.primary; }) diff --git a/resources/js/entity-list/components/EntityList.vue b/resources/js/entity-list/components/EntityList.vue index 4e268d7ce..43b1b96fb 100644 --- a/resources/js/entity-list/components/EntityList.vue +++ b/resources/js/entity-list/components/EntityList.vue @@ -3,7 +3,7 @@ import { FilterManager } from "@/filters/FilterManager"; import { EntityList } from "../EntityList"; import { - CommandData, + CommandData, EntityListMultiformData, EntityStateValueData, FilterData } from "@/types"; @@ -47,7 +47,7 @@ import { DropdownMenuPortal } from "reka-ui"; import CommandDropdownItems from "@/commands/components/CommandDropdownItems.vue"; import { Badge } from "@/components/ui/badge"; - import { Link } from "@inertiajs/vue3"; + import { Link, router } from "@inertiajs/vue3"; import EntityListSearch from "@/entity-list/components/EntityListSearch.vue"; import StickyTop from "@/components/StickyTop.vue"; import { cn } from "@/utils/cn"; @@ -150,6 +150,33 @@ }); } + async function onCreate(event: MouseEvent, form?: EntityListMultiformData) { + if(event.metaKey || event.ctrlKey || event.shiftKey) { + return; + } + const { entityKey } = props; + + event.preventDefault(); + + if(props.entityList.config.quickCreationForm) { + await props.commands.send({ hasForm: true } as CommandData, { + postCommand: route('code16.sharp.api.list.command.quick-creation-form.store', { + entityKey: form ? `${entityKey}:${form.key}` : entityKey, + }), + getForm: route('code16.sharp.api.list.command.quick-creation-form.create', { + entityKey: form ? `${entityKey}:${form.key}` : entityKey, + }), + query: props.entityList.query, + entityKey, + }); + } else { + router.visit(route('code16.sharp.form.create', { + parentUri: getAppendableParentUri(), + entityKey: form ? `${entityKey}:${form.key}` : entityKey, + })); + } + } + async function onInstanceCommand(command: CommandData, instanceId: InstanceId) { const { commands, entityKey } = props; @@ -376,10 +403,12 @@ @@ -387,11 +416,12 @@