Skip to content

Commit

Permalink
Add missing default actions (Replicate / Preview / Impersonate) (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
milewski authored Oct 21, 2023
1 parent 4d105d0 commit 7e2e37a
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 309 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ class UserResource extends Resource
}
```

## Configuration

You can customize the icons by adding the following to your nova config file:

```php
// config/nova.php

'vendors' => [
'icon_action_toolbar' => [
'icons' => [
'replicate' => 'duplicate',
'preview' => 'document-search',
'impersonate' => 'finger-print',
'delete_resource' => '<svg>...</svg>',
],
]
]
```

## ⭐️ Show Your Support

Please give a ⭐️ if this project helped you!
Expand Down
12 changes: 12 additions & 0 deletions config/nova.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types = 1);

return [
'icons' => [
'replicate' => 'duplicate',
'preview' => 'document-search',
'impersonate' => 'finger-print',
'delete_resource' => 'trash',
],
];
2 changes: 1 addition & 1 deletion dist/css/tool.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/js/tool.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
"nova:install": "npm --prefix='../../vendor/laravel/nova' ci"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.2.22",
"@vue/compiler-sfc": "^3.3.6",
"laravel-mix": "^6.0.41",
"mix-tailwindcss": "^1.3.0",
"sass": "^1.69.2",
"sass": "^1.69.4",
"sass-loader": "^13.3.2",
"tailwindcss": "^3.3.3",
"vue-loader": "^17.3.0"
Expand Down
98 changes: 96 additions & 2 deletions resources/js/components/ActionDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
:data="actionResponseData"
/>

<IconActionToolbar :actions="actions" @click="handleActionClick" :standalone="true"/>
<IconActionToolbar :parent-type="parentType" :actions="availableActions" @click="onClick" :standalone="true"/>

</div>

Expand All @@ -35,9 +35,11 @@
<script setup>
import { useActions } from '@/composables/useActions'
import { useLocalization } from '@/composables/useLocalization'
import IconActionToolbar from './IconActionToolbar.vue'
import { computed, getCurrentInstance } from 'vue'
const emitter = defineEmits([ 'actionExecuted' ])
const emitter = defineEmits([ 'actionExecuted', 'show-preview' ])
const props = defineProps({
resourceName: {},
Expand Down Expand Up @@ -65,7 +67,27 @@
actionResponseData,
} = useActions(props, emitter, Nova.store)
const { __ } = useLocalization()
const instance = getCurrentInstance()
const runAction = () => executeAction(() => emitter('actionExecuted'))
const parentType = instance.parent.vnode.type.__file
const onClick = event => {
const action = availableActions.value.find(element => element.uriKey === event)
if (typeof action.onClick === 'function') {
action.onClick()
} else {
handleActionClick(event)
}
}
const handleResponseModalConfirm = () => {
closeResponseModal()
Expand All @@ -77,4 +99,76 @@
emitter('actionExecuted')
}
const availableActions = computed(() => {
const actions = [ ...props.actions ]
const resource = instance.parent?.props?.resource
const currentUser = Nova.store.getters[ 'currentUser' ]
const config = Nova.config('icon_action_toolbar')
if (resource) {
if (resource.authorizedToReplicate) {
actions.push({
name: __('Replicate'),
uriKey: '__replicate-action__',
iconActionToolbar: { icon: config.icons.replicate },
onClick: () => {
const url = instance.ctx.$url(`/resources/${ props.resourceName }/${ resource.id.value }/replicate`, {
viaResource: props.viaResource,
viaResourceId: props.viaResourceId,
viaRelationship: props.viaRelationship,
})
Nova.visit(url)
},
})
}
if (resource.authorizedToView && resource.previewHasFields) {
actions.push({
name: __('Preview'),
uriKey: '__preview-action__',
iconActionToolbar: { icon: config.icons.preview },
onClick: () => instance.parent.emit('show-preview'),
})
}
if (currentUser.canImpersonate && resource.authorizedToImpersonate) {
actions.push({
name: __('Impersonate'),
uriKey: '__impersonate-action__',
iconActionToolbar: { icon: config.icons.impersonate },
onClick: () => instance.parent.ctx.startImpersonating({
resource: props.resourceName,
resourceId: resource.id.value,
}),
})
}
if (resource.authorizedToDelete && !resource.softDeleted) {
actions.push({
name: __('Delete Resource'),
uriKey: '__delete-resource-action__',
iconActionToolbar: { icon: config.icons.delete_resource },
onClick: () => instance.parent.ctx.openDeleteModal(),
})
}
}
return actions
})
</script>
22 changes: 14 additions & 8 deletions resources/js/components/ActionSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,19 @@
determineActionStrategy()
}
const actionsForSelect = computed(() => [
...availableActions.value,
...availablePivotActions.value.map(a => ({
group: props.pivotName,
uriKey: a.uriKey,
name: a.name,
})),
])
const actionsForSelect = computed(() => {
const actions = [
...availableActions.value,
...availablePivotActions.value.map(a => ({
group: props.pivotName,
uriKey: a.uriKey,
name: a.name,
})),
]
return actions.filter(action => action.showOnTableRow === false)
})
</script>
48 changes: 24 additions & 24 deletions resources/js/components/IconActionToolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,35 @@

<div v-if="actions.length > 0"
:class="{ 'rounded': standalone, 'rounded-lg bg-gray-100 dark:bg-gray-700': !standalone }"
class="flex ml-1 dark:focus:ring-gray-600 justify-evenly">
class="flex dark:focus:ring-gray-600 justify-evenly">

<div v-for="{ iconActionToolbar, destructive, uriKey, name } of actions">

<Dropdown
v-if="iconActionToolbar"
<button
v-tooltip="name"
:handle-internal-clicks="false"
v-if="iconActionToolbar"
type="button"
@click.stop="() => $emit('click', uriKey)"
:class="{
'rounded hover:bg-gray-200 dark:hover:bg-gray-800': standalone,
'rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700': !standalone,
'hover:text-red-500': destructive,
'hover:text-primary-500': !destructive,
'hover:text-red-500': destructive && !isDetailView,
'hover:text-primary-500': !destructive && !isDetailView,
'rounded hover:bg-gray-200 dark:hover:bg-gray-800 focus:outline-none focus:ring px-3 hover:text-gray-400': standalone && isDetailView
}"
class="flex h-9 hover-element"
:trigger-override-function="() => $emit('click', uriKey)">

<div class="space-x-2 flex justify-center items-center"
:class="{ 'px-3 h-9': standalone, 'px-2': !standalone }">

<Icon v-if="typeof iconActionToolbar.icon !== 'string'" type="cube-transparent"/>
class="toolbar-button px-2 disabled:opacity-50 disabled:pointer-events-none">

<template v-else-if="iconActionToolbar.icon?.trim()?.startsWith('<svg')">
<div v-html="iconActionToolbar.icon"/>
</template>
<Icon v-if="typeof iconActionToolbar.icon !== 'string'" type="cube-transparent"/>

<Icon v-else-if="iconActionToolbar.icon" :type="iconActionToolbar.icon"/>
<template v-else-if="iconActionToolbar.icon?.trim()?.startsWith('<svg')">
<div v-html="iconActionToolbar.icon"/>
</template>

<div v-if="iconActionToolbar.label">
{{ iconActionToolbar.label }}
</div>
<Icon v-else-if="iconActionToolbar.icon" :type="iconActionToolbar.icon"/>

<div class="ml-1" v-if="iconActionToolbar.label">
{{ iconActionToolbar.label }}
</div>

</Dropdown>
</button>

</div>

Expand All @@ -48,7 +42,13 @@
export default {
emits: [ 'click' ],
props: [ 'actions', 'standalone' ],
props: [ 'actions', 'standalone', 'parentType' ],
computed: {
isDetailView() {
return Nova.$router.page.component === 'Nova.Detail'
&& this.parentType === 'DetailActionDropdown.vue'
},
},
}
</script>
Expand Down
11 changes: 11 additions & 0 deletions src/IconActionToolbarServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,20 @@ public function boot(): void

Nova::serving(function (ServingNova $event): void {

Nova::provideToScript([
'icon_action_toolbar' => config('nova.vendors.icon_action_toolbar'),
]);

Nova::script('icon-action-toolbar', __DIR__ . '/../dist/js/tool.js');
Nova::style('icon-action-toolbar', __DIR__ . '/../dist/css/tool.css');

});
}

public function register(): void
{
$this->mergeConfigFrom(
__DIR__ . '/../config/nova.php', 'nova.vendors.icon_action_toolbar',
);
}
}
Loading

0 comments on commit 7e2e37a

Please sign in to comment.