Skip to content

Commit

Permalink
[5.x] Field action modals (#11129)
Browse files Browse the repository at this point in the history
Co-authored-by: Jack Sleight <jacksleight@gmail.com>
  • Loading branch information
jasonvarga and jacksleight authored Nov 20, 2024
1 parent 63be821 commit 7ad5877
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 2 deletions.
2 changes: 2 additions & 0 deletions resources/js/bootstrap/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import Modal from '../components/Modal.vue';
import ConfirmationModal from '../components/modals/ConfirmationModal.vue';
import FavoriteCreator from '../components/FavoriteCreator.vue';
import KeyboardShortcutsModal from '../components/modals/KeyboardShortcutsModal.vue';
import FieldActionModal from '../components/field-actions/FieldActionModal.vue';
import ResourceDeleter from '../components/ResourceDeleter.vue';
import Stack from '../components/stacks/Stack.vue';
import StackTest from '../components/stacks/StackTest.vue';
Expand Down Expand Up @@ -144,6 +145,7 @@ Vue.component('confirmation-modal', ConfirmationModal);
Vue.component('favorite-creator', FavoriteCreator);
Vue.component('keyboard-shortcuts-modal', KeyboardShortcutsModal);
Vue.component('resource-deleter', ResourceDeleter);
Vue.component('field-action-modal', FieldActionModal);

Vue.component('stack', Stack);
Vue.component('stack-test', StackTest);
Expand Down
33 changes: 31 additions & 2 deletions resources/js/components/field-actions/FieldAction.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import modal from './modal';

export default class FieldAction {
#payload;
#run;
#visible;
#visibleWhenReadOnly;
#icon;
#quick;
#confirm;

constructor(action, payload) {
this.#payload = payload;
this.#run = action.run;
this.#confirm = action.confirm;
this.#visible = action.visible ?? true;
this.#visibleWhenReadOnly = action.visibleWhenReadOnly ?? false;
this.#icon = action.icon ?? 'image';
Expand All @@ -32,7 +36,32 @@ export default class FieldAction {
return typeof this.#icon === 'function' ? this.#icon(this.#payload) : this.#icon;
}

run() {
this.#run(this.#payload);
async run() {
let payload = {...this.#payload};

if (this.#confirm) {
const confirmation = await modal(this.#modalProps());
if (!confirmation.confirmed) return;
payload = {...payload, confirmation};
}

const response = this.#run(payload);

if (response instanceof Promise) {
const progress = this.#payload.vm.$progress;
const name = this.#payload.fieldPathPrefix ?? this.#payload.handle;
progress.loading(name, true);
response.finally(() => progress.loading(name, false));
}
}

#modalProps() {
let props = this.#confirm === true ? {} : {...this.#confirm};

if (! props.title) {
props.title = this.title;
}

return props;
}
}
152 changes: 152 additions & 0 deletions resources/js/components/field-actions/FieldActionModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<template>

<div>
<confirmation-modal
:title="title"
:danger="dangerous"
:buttonText="buttonText"
:busy="resolving || processing"
@confirm="confirm"
@cancel="cancel"
>
<div class="min-h-20">

<div v-if="bodyText" v-text="bodyText" :class="{ 'mb-4': warningText || hasFields }" />

<div v-if="warningText" v-text="warningText" class="text-red-500" :class="{ 'mb-4': hasFields }" />

<publish-container
v-if="hasFields && !resolving"
:name="containerName"
:blueprint="blueprint"
:values="values"
:meta="meta"
:errors="errors"
@updated="values = $event"
>
<publish-fields
slot-scope="{ setFieldValue, setFieldMeta }"
:fields="blueprint.tabs[0].fields"
@updated="setFieldValue"
@meta-updated="setFieldMeta"
/>
</publish-container>

</div>
</confirmation-modal>
</div>

</template>

<script>
import uniqid from "uniqid";
export default {
props: {
fields: {
type: Object,
},
title: {
type: String,
},
buttonText: {
type: String,
},
text: {
type: String,
},
warningText: {
type: String,
},
dangerous: {
type: Boolean,
default: false,
},
},
data() {
return {
resolving: this.hasFields,
processing: false,
blueprint: [],
values: {},
meta: {},
error: null,
errors: {},
bodyText: null,
containerName: `field-action-modal-${uniqid()}`,
}
},
mounted() {
this.bodyText = this.initializeBodyText();
this.initialize();
},
computed: {
hasFields() {
return this.fields && Object.keys(this.fields).length > 0;
}
},
methods: {
initialize() {
if (!this.hasFields) {
return;
}
this.resolving = true;
this.$axios.post(cp_url(`field-action-modal/resolve`), {
fields: this.fields,
}).then(response => {
this.blueprint = { tabs: [{ fields: response.data.fields }]};
this.values = response.data.values;
this.meta = response.data.meta;
this.resolving = false;
});
},
confirm() {
if (!this.hasFields) {
this.$emit('confirm');
return;
}
this.processing = true;
this.$axios.post(cp_url('field-action-modal/process'), {
fields: this.fields,
values: this.values,
}).then(response => {
this.$emit('confirm', response.data);
}).catch(e => {
if (e.response && e.response.status === 422) {
const { message, errors } = e.response.data;
this.error = message;
this.errors = errors;
this.$toast.error(message);
} else if (e.response) {
this.$toast.error(e.response.data.message);
} else {
this.$toast.error(__('Something went wrong'));
}
}).finally(() => this.processing = false);
},
cancel() {
this.$emit('cancel')
},
initializeBodyText() {
if (this.text) return this.text;
if (this.warningText || this.hasFields) return null;
return __('Are you sure?');
}
},
}
</script>
16 changes: 16 additions & 0 deletions resources/js/components/field-actions/modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default function(props) {
return new Promise((resolve) => {
const component = Statamic.$components.append('field-action-modal', { props });
const close = () => Statamic.$components.destroy(component.id);

component.on('confirm', (data = {}) => {
resolve({ ...data, confirmed: true });
close();
});

component.on('cancel', () => {
resolve({ confirmed: false });
close();
});
});
}
6 changes: 6 additions & 0 deletions routes/cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use Statamic\Http\Controllers\CP\CpController;
use Statamic\Http\Controllers\CP\DashboardController;
use Statamic\Http\Controllers\CP\DuplicatesController;
use Statamic\Http\Controllers\CP\FieldActionModalController;
use Statamic\Http\Controllers\CP\Fields\BlueprintController;
use Statamic\Http\Controllers\CP\Fields\FieldsController;
use Statamic\Http\Controllers\CP\Fields\FieldsetController;
Expand Down Expand Up @@ -319,6 +320,11 @@
Route::get('dictionaries/{dictionary}', DictionaryFieldtypeController::class)->name('dictionary-fieldtype');
});

Route::group(['prefix' => 'field-action-modal'], function () {
Route::post('resolve', [FieldActionModalController::class, 'resolve'])->name('resolve');
Route::post('process', [FieldActionModalController::class, 'process'])->name('process');
});

Route::group(['prefix' => 'api', 'as' => 'api.'], function () {
Route::resource('addons', AddonsApiController::class)->only('index');
Route::resource('templates', TemplatesController::class)->only('index');
Expand Down
48 changes: 48 additions & 0 deletions src/Http/Controllers/CP/FieldActionModalController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Statamic\Http\Controllers\CP;

use Illuminate\Http\Request;
use Statamic\Fields\Fields;

class FieldActionModalController extends CpController
{
public function resolve(Request $request)
{
$fields = $this
->getFields($request->fields)
->preProcess();

return [
'fields' => $fields->toPublishArray(),
'values' => $fields->values(),
'meta' => $fields->meta(),
];
}

public function process(Request $request)
{
$fields = $this
->getFields($request->fields)
->addValues($request->values);

$fields->validate();

$processed = $fields->process()->values();

$fields->preProcess();

return [
'values' => $fields->values(),
'meta' => $fields->meta(),
'processed' => $processed,
];
}

private function getFields($fieldItems)
{
return new Fields(
collect($fieldItems)->map(fn ($field, $handle) => compact('handle', 'field'))
);
}
}

0 comments on commit 7ad5877

Please sign in to comment.