Skip to content
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

[5.x] Field action modals #11129

Merged
merged 77 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
1cc2d38
WIP
jacksleight Apr 15, 2024
1e9867c
Merge branch '5.x' into feature/field-actions
jacksleight Jun 21, 2024
d4acda0
WIP
jacksleight Jun 21, 2024
ab1c934
WIP
jacksleight Jun 21, 2024
42e5d49
WIP
jacksleight Jun 21, 2024
c1d3fcb
WIP
jacksleight Jun 21, 2024
a99d386
WIP
jacksleight Jun 24, 2024
9be3506
Merge branch '5.x' into feature/field-actions
jacksleight Sep 5, 2024
97bf8e4
Visibilty callback
jacksleight Sep 5, 2024
bbcd085
Set payloads
jacksleight Sep 5, 2024
330cd53
WIP
jacksleight Sep 19, 2024
2d61913
WIP
jacksleight Sep 20, 2024
bc84bdd
Merge branch '5.x' into feature/field-actions
jacksleight Sep 20, 2024
a448f97
Tweak payload keys
jacksleight Sep 23, 2024
cb53588
Merge branch 'feature/field-actions' of github.com:jacksleight/statam…
jacksleight Sep 23, 2024
5002d37
Naming tweaks
jacksleight Sep 24, 2024
eed8334
Naming tweaks, round two
jacksleight Sep 24, 2024
d7d3fe4
Ensure dropdown buttons dont submit forms
jacksleight Sep 24, 2024
d081de7
Option to resolve icons using callback
jacksleight Nov 6, 2024
8c7c595
Merge branch '5.x' into feature/field-actions
jasonvarga Nov 8, 2024
90462b2
marking an action as quick without an icon breaks. this defaults it t…
jasonvarga Nov 8, 2024
6ac8c38
noticed that there is a white background. when displayed on non-white…
jasonvarga Nov 8, 2024
4fd3dc7
rename
jasonvarga Nov 8, 2024
2ba3164
organize/rename
jasonvarga Nov 8, 2024
e49eeac
dont need the custom components only being used in one place
jasonvarga Nov 8, 2024
5e49e49
bracies
jasonvarga Nov 8, 2024
3232403
import dropdown actions as components. no need to be global.
jasonvarga Nov 8, 2024
af782a0
pass the payload along with the filter function
jasonvarga Nov 8, 2024
020bdc1
fix error
jasonvarga Nov 8, 2024
1f65ba7
more of 90462b26
jasonvarga Nov 8, 2024
546f2b7
more calling field actions field actions
jasonvarga Nov 8, 2024
704da52
nitpick
jasonvarga Nov 8, 2024
06c4f00
merge internal and custom actions
jasonvarga Nov 8, 2024
0a47981
self closing
jasonvarga Nov 8, 2024
e16e990
Refactor to class ...
jasonvarga Nov 15, 2024
dc8651d
change to title prop
jasonvarga Nov 15, 2024
ae740cf
nitpick
jasonvarga Nov 15, 2024
acacecf
action.display isnt a thing
jasonvarga Nov 15, 2024
fbde34f
We never care about non-visible actions
jasonvarga Nov 15, 2024
7f59960
nitpick
jasonvarga Nov 15, 2024
89466b6
oops from dc8651d9
jasonvarga Nov 15, 2024
089a76f
Add bits from #10833
jasonvarga Nov 15, 2024
cca2c56
move modal out of $fieldActions and into run method
jasonvarga Nov 15, 2024
40b5b06
pass missing confirmed bool
jasonvarga Nov 15, 2024
36045b6
dont worry about dirty stuff
jasonvarga Nov 15, 2024
be70422
extract variable
jasonvarga Nov 15, 2024
f2c899b
nitpick
jasonvarga Nov 15, 2024
7668295
always use the same component
jasonvarga Nov 19, 2024
f869093
body text has a default when both are missing
jasonvarga Nov 19, 2024
3ff8ae4
always close the modal on confirm
jasonvarga Nov 19, 2024
72114b2
if no fields, confirming will close
jasonvarga Nov 19, 2024
3be1965
show field actions in bard/replicator subfields
jasonvarga Nov 19, 2024
3f84785
Merge branch 'feature/field-actions' into field-action-modals
jasonvarga Nov 19, 2024
21064d2
actions can provide a confirm object to get a modal instead of doing …
jasonvarga Nov 19, 2024
733fbbb
loading state is handled automatically if run returns a promise
jasonvarga Nov 19, 2024
bd39989
handle reject too
jasonvarga Nov 19, 2024
d6d3c77
allow confirm: true
jasonvarga Nov 19, 2024
f1aca3e
words
jasonvarga Nov 19, 2024
cc3015e
nitpick
jasonvarga Nov 19, 2024
9b00510
nitpick
jasonvarga Nov 19, 2024
c00a1ff
initial body text better. if there are fields dont set a default
jasonvarga Nov 19, 2024
e3e16f2
modal title defaults to the action title
jasonvarga Nov 19, 2024
9a01bbf
do better
jasonvarga Nov 19, 2024
61bf410
call it text
jasonvarga Nov 19, 2024
9ab940d
handle situations when the field reference doesnt exist - like lazy l…
jasonvarga Nov 20, 2024
fed5bfe
avoid rendering components if we're just going to hide it with css.
jasonvarga Nov 20, 2024
cff367f
specific
jasonvarga Nov 20, 2024
4333750
Merge branch 'feature/field-actions' into field-action-modals
jasonvarga Nov 20, 2024
f922f2d
make reactive again by putting it back into a computed. oopsie i brok…
jasonvarga Nov 20, 2024
2e7f147
dont bother rendering actions if they arent going to be visible
jasonvarga Nov 20, 2024
6cc29f4
Merge branch 'feature/field-actions' into field-action-modals
jasonvarga Nov 20, 2024
cc98155
fix modals in modals (in modals (in modals (in modals...)))
jasonvarga Nov 20, 2024
4228001
add isReadOnly to payload
jasonvarga Nov 20, 2024
0973f48
actions arent shown by default on read only fields but can be opted in
jasonvarga Nov 20, 2024
cb037a3
Merge branch 'feature/field-actions' into field-action-modals
jasonvarga Nov 20, 2024
1e1409e
nitpick
jasonvarga Nov 20, 2024
2e1633d
Merge branch '5.x' into field-action-modals
jasonvarga Nov 20, 2024
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
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'))
);
}
}
Loading