Skip to content

Allow sub-entities #605

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
16 changes: 8 additions & 8 deletions demo/app/Sharp/Entities/PostBlockEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ class PostBlockEntity extends SharpEntity
protected ?string $policy = PostBlockPolicy::class;
protected string $label = 'Block';

public function getMultiforms(): array
{
return [
'text' => [PostBlockTextForm::class, 'Text block'],
'visuals' => [PostBlockVisualsForm::class, 'Visuals block'],
'video' => [PostBlockVideoForm::class, 'Video block'],
];
}
// public function getMultiforms(): array
// {
// return [
// 'text' => [PostBlockTextForm::class, 'Text block'],
// 'visuals' => [PostBlockVisualsForm::class, 'Visuals block'],
// 'video' => [PostBlockVideoForm::class, 'Video block'],
// ];
// }
}
12 changes: 12 additions & 0 deletions demo/app/Sharp/Entities/PostBlockTextEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace App\Sharp\Entities;

use App\Sharp\Posts\Blocks\PostBlockTextForm;

class PostBlockTextEntity extends PostBlockEntity
{
protected ?string $form = PostBlockTextForm::class;
protected ?string $icon = 'lucide-text';
protected string $label = 'Text block';
}
12 changes: 12 additions & 0 deletions demo/app/Sharp/Entities/PostBlockVideoEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace App\Sharp\Entities;

use App\Sharp\Posts\Blocks\PostBlockVideoForm;

class PostBlockVideoEntity extends PostBlockEntity
{
protected ?string $form = PostBlockVideoForm::class;
protected ?string $icon = 'lucide-video';
protected string $label = 'Video block';
}
14 changes: 14 additions & 0 deletions demo/app/Sharp/Entities/PostBlockVisualsEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace App\Sharp\Entities;

use App\Sharp\Posts\Blocks\PostBlockVisualsForm;
use App\Sharp\Posts\Blocks\PostBlockVisualsShow;

class PostBlockVisualsEntity extends PostBlockEntity
{
protected ?string $form = PostBlockVisualsForm::class;
protected ?string $show = PostBlockVisualsShow::class;
protected ?string $icon = 'lucide-images';
protected string $label = 'Visual block';
}
9 changes: 8 additions & 1 deletion demo/app/Sharp/Posts/Blocks/PostBlockList.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

use App\Models\Media;
use App\Models\PostBlock;
use App\Sharp\Entities\PostBlockTextEntity;
use App\Sharp\Entities\PostBlockVideoEntity;
use App\Sharp\Entities\PostBlockVisualsEntity;
use Code16\Sharp\EntityList\Eloquent\SimpleEloquentReorderHandler;
use Code16\Sharp\EntityList\Fields\EntityListField;
use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer;
Expand All @@ -29,7 +32,11 @@ protected function buildList(EntityListFieldsContainer $fields): void

public function buildListConfig(): void
{
$this->configureMultiformAttribute('type')
$this->configureSubEntities('type', [
'text' => PostBlockTextEntity::class,
'video' => PostBlockVideoEntity::class,
'visuals' => PostBlockVisualsEntity::class,
])
->configureReorderable(new SimpleEloquentReorderHandler(PostBlock::class))
->configureQuickCreationForm();
}
Expand Down
62 changes: 62 additions & 0 deletions demo/app/Sharp/Posts/Blocks/PostBlockVisualsShow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace App\Sharp\Posts\Blocks;

use App\Models\PostBlock;
use Code16\Sharp\Form\Eloquent\Uploads\Transformers\SharpUploadModelFormAttributeTransformer;
use Code16\Sharp\Show\Fields\SharpShowFileField;
use Code16\Sharp\Show\Fields\SharpShowListField;
use Code16\Sharp\Show\Fields\SharpShowTextField;
use Code16\Sharp\Show\Layout\ShowLayout;
use Code16\Sharp\Show\Layout\ShowLayoutColumn;
use Code16\Sharp\Show\Layout\ShowLayoutSection;
use Code16\Sharp\Show\SharpShow;
use Code16\Sharp\Utils\Fields\FieldsContainer;

class PostBlockVisualsShow extends SharpShow
{
protected function buildShowFields(FieldsContainer $showFields): void
{
$showFields
->addField(
SharpShowListField::make('files')
->setLabel('Visuals')
->addItemField(
SharpShowFileField::make('file')
)
->addItemField(
SharpShowTextField::make('legend')
->setLabel('Legend')
)
);
}

protected function buildShowLayout(ShowLayout $showLayout): void
{
$showLayout
->addSection(function (ShowLayoutSection $section) {
$section
->addColumn(12, function (ShowLayoutColumn $column) {
$column
->withListField('files', function (ShowLayoutColumn $item) {
$item->withFields('file')
->withField('legend');
});
});
});
}

public function find(mixed $id): array
{
$postBlock = PostBlock::findOrFail($id);

return $this
->setCustomTransformer('files', new SharpUploadModelFormAttributeTransformer())
->transform($postBlock);
}

public function delete(mixed $id): void
{
PostBlock::findOrFail($id)->delete();
}
}
6 changes: 3 additions & 3 deletions resources/js/Layouts/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@
<DropdownMenuSeparator class="first:hidden" />
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Sun class="w-4 h-4 mr-2 dark:hidden" />
<Moon class="hidden w-4 h-4 mr-2 dark:block" />
<Sun class="w-4 h-4 dark:hidden" />
<Moon class="hidden w-4 h-4 dark:block" />
{{ __('sharp::action_bar.color-mode-dropdown.label') }}
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
Expand All @@ -233,7 +233,7 @@
<form :action="route('code16.sharp.logout')" method="post">
<input name="_token" :value="getCsrfToken()" type="hidden">
<DropdownMenuItem type="submit" @click="$event.target.closest('form').submit()">
<LogOut class="w-4 h-4 mr-2" />
<LogOut class="w-4 h-4" />
{{ __('sharp::menu.logout_label') }}
</DropdownMenuItem>
</form>
Expand Down
2 changes: 1 addition & 1 deletion resources/js/Pages/Show/Show.vue
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@
:collapsable="section.collapsable"
:entity-key="entityKey"
:instance-id="instanceId"
:is-right-col="columnIndex === section.columns.length - 1"
:is-right-col="columnIndex > 0 && columnIndex === section.columns.length - 1"
:row="row"
@reordering="onEntityListReordering(fieldLayout.key, $event)"
/>
Expand Down
33 changes: 2 additions & 31 deletions resources/js/entity-list/EntityList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import {
EntityStateValueData,
FilterData,
} from "@/types";
import { getAppendableParentUri, route } from "@/utils/url";
import { EntityListInstance, InstanceId } from "./types";

export class EntityList implements EntityListData {
authorizations: EntityListData['authorizations'];
config: EntityListData['config'];
data: EntityListData['data'];
fields: EntityListData['fields'];
forms: EntityListData['forms'];
subEntities: EntityListData['subEntities'];
meta: EntityListData['meta'];
pageAlert: EntityListData['pageAlert'];
query: EntityListData['query'];
Expand Down Expand Up @@ -110,31 +109,6 @@ export class EntityList implements EntityListData {
return instance[this.config.instanceIdAttribute];
}

instanceUrl(instance: EntityListInstance): string | null {
const entityKey = this.entityKey;
const instanceId = this.instanceId(instance);

if(!this.authorizations.view.includes(instanceId)) {
return null;
}

const multiform = this.forms && Object.values(this.forms).find(form => form.instances.includes(instanceId));

if(this.config.hasShowPage) {
return route('code16.sharp.show.show', {
parentUri: getAppendableParentUri(),
entityKey: multiform ? `${entityKey}:${multiform.key}` : entityKey,
instanceId,
});
}

return route('code16.sharp.form.edit', {
parentUri: getAppendableParentUri(),
entityKey: multiform ? `${entityKey}:${multiform.key}` : entityKey,
instanceId,
});
}

instanceState(instance: EntityListInstance): string | number | null {
return this.config.state
? instance[this.config.state.attribute]
Expand All @@ -153,10 +127,7 @@ export class EntityList implements EntityListData {
}

instanceCanDelete(instance: EntityListInstance): boolean {
if(Array.isArray(this.authorizations.delete)) {
return this.authorizations.delete?.includes(this.instanceId(instance));
}
return !!this.authorizations.delete;
return instance._meta.authorizations.delete;
}

instanceCommands(instance: EntityListInstance): Array<Array<CommandData>> | undefined {
Expand Down
35 changes: 19 additions & 16 deletions resources/js/entity-list/components/EntityList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { FilterManager } from "@/filters/FilterManager";
import { EntityList } from "../EntityList";
import {
CommandData, EntityListFieldData, EntityListMultiformData, EntityListQueryParamsData,
CommandData, EntityListFieldData, EntityListSubEntityData, EntityListQueryParamsData,
EntityStateValueData,
FilterData
} from "@/types";
Expand Down Expand Up @@ -68,6 +68,7 @@
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { useElementVisibility } from "@vueuse/core";
import StateBadge from "@/components/ui/StateBadge.vue";
import Icon from "@/components/ui/Icon.vue";
// import StateBadgeTest from "@/components/dev/StateBadgeTest.vue";

const props = withDefaults(defineProps<{
Expand Down Expand Up @@ -175,7 +176,7 @@
});
}

async function onCreate(event: MouseEvent, form?: EntityListMultiformData) {
async function onCreate(event: MouseEvent, subEntity?: EntityListSubEntityData) {
if(event.metaKey || event.ctrlKey || event.shiftKey) {
return;
}
Expand All @@ -186,19 +187,18 @@
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,
entityKey,
formEntityKey: subEntity ? subEntity.entityKey : entityKey,
}),
getForm: route('code16.sharp.api.list.command.quick-creation-form.create', {
entityKey: form ? `${entityKey}:${form.key}` : entityKey,
entityKey,
formEntityKey: subEntity ? subEntity.entityKey : entityKey,
}),
query: props.entityList.query,
entityKey: form ? `${entityKey}:${form.key}` : entityKey,
entityKey: subEntity ? subEntity.entityKey : entityKey,
});
} else {
router.visit(route('code16.sharp.form.create', {
parentUri: getAppendableParentUri(),
entityKey: form ? `${entityKey}:${form.key}` : entityKey,
}));
router.visit(subEntity ? subEntity.formCreateUrl : props.entityList.config.formCreateUrl);
}
}

Expand Down Expand Up @@ -469,7 +469,7 @@
</template>

<template v-if="showCreateButton && entityList.authorizations.create && !reordering && !selecting">
<template v-if="entityList.forms && Object.values(entityList.forms).length">
<template v-if="entityList.subEntities?.length">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button class="h-8" size="sm">
Expand All @@ -478,13 +478,16 @@
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<template v-for="form in Object.values(entityList.forms).filter(f => !!f.label)">
<template v-for="subEntity in entityList.subEntities">
<DropdownMenuItem
as="a"
:href="route('code16.sharp.form.create', { parentUri: getAppendableParentUri(), entityKey: `${entityKey}:${form.key}` })"
@click="onCreate($event, form)"
:href="subEntity.formCreateUrl"
@click="onCreate($event, subEntity)"
>
{{ form.label }}
<template v-if="subEntity.icon">
<Icon class="size-4" :icon="subEntity.icon" />
</template>
{{ subEntity.label }}
</DropdownMenuItem>
</template>
</DropdownMenuContent>
Expand Down Expand Up @@ -786,10 +789,10 @@
{{ item[field.key] }}
</template>
</template>
<template v-if="fieldIndex === 0 && entityList.instanceUrl(item) && !selecting && !reordering">
<template v-if="fieldIndex === 0 && item._meta.url && !selecting && !reordering">
<Link class="absolute inset-0 ring-ring ring-inset focus-visible:outline-none focus-visible:ring-2 focus:group-data-highlighted/row:ring-2"
data-row-action
:href="entityList.instanceUrl(item)"
:href="item._meta.url"
></Link>
</template>
</TableCell>
Expand Down
24 changes: 15 additions & 9 deletions resources/js/types/generated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,6 @@ export type EmbedFormData = {
layout: FormLayoutData | null;
};
export type EntityListAuthorizationsData = {
view: Array<number | string>;
delete: Array<number | string>;
create: boolean;
reorder: boolean;
};
Expand All @@ -147,7 +145,8 @@ export type EntityListConfigData = {
hasShowPage: boolean;
deleteConfirmationText: string;
deleteHidden: boolean;
multiformAttribute: string | null;
formCreateUrl: string;
subEntityAttribute: string | null;
createButtonLabel: string | null;
quickCreationForm: boolean;
filters: ConfigFiltersData | null;
Expand All @@ -159,10 +158,10 @@ export type EntityListData = {
authorizations: EntityListAuthorizationsData;
config: EntityListConfigData;
fields: Array<EntityListFieldData>;
data: Array<{ [key: string]: any }>;
data: Array<{ [key: string]: any; _meta: EntityListItemMeta }>;
filterValues: FilterValuesData;
query: EntityListQueryParamsData | null;
forms: { [key: string]: EntityListMultiformData } | null;
subEntities: Array<EntityListSubEntityData> | null;
meta: PaginatorMetaData | null;
pageAlert: PageAlertData | null;
};
Expand All @@ -176,10 +175,9 @@ export type EntityListFieldData = {
html: boolean | null;
tooltip: string | null;
};
export type EntityListMultiformData = {
key: string;
label: string;
instances: Array<number | string>;
export type EntityListItemMeta = {
url: string | null;
authorizations: InstanceAuthorizationsData;
};
export type EntityListQueryParamsData = {
search?: string;
Expand All @@ -189,6 +187,14 @@ export type EntityListQueryParamsData = {
} & {
[filterKey: string]: string;
};
export type EntityListSubEntityData = {
key: string;
entityKey: string;
label: string;
icon: IconData | null;
formCreateUrl: string | null;
authorizations: EntityListAuthorizationsData;
};
export type EntityStateData = {
attribute: string;
values: Array<EntityStateValueData>;
Expand Down
Loading
Loading