Skip to content

Commit 721d154

Browse files
committed
Add Modifiers
1 parent 21e5f63 commit 721d154

File tree

12 files changed

+445
-53
lines changed

12 files changed

+445
-53
lines changed

dist/js/tool.js

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/js/tool.js.LICENSE.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**!
2+
* Sortable 1.14.0
3+
* @author RubaXa <[email protected]>
4+
* @author owenm <[email protected]>
5+
* @license MIT
6+
*/

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@
1515
"postcss": "^8.3.11",
1616
"vue-loader": "^16.8.3"
1717
},
18-
"dependencies": {}
18+
"dependencies": {
19+
"vuedraggable": "^4.1.0"
20+
}
1921
}

resources/js/pages/Configure.vue

Lines changed: 130 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -54,52 +54,114 @@
5454
</card>
5555

5656
<card class="p-8 space-y-4">
57-
<p v-if="resource">
58-
Choose which data to fill the appropriate fields of the chosen resource. The columns from your uploaded
59-
file have been auto-matched to the resource fields with the same name.
60-
</p>
57+
<template v-if="resource">
58+
<p>
59+
Choose which data to fill the appropriate fields of the chosen resource. The columns from your uploaded
60+
file have been auto-matched to the resource fields with the same name.
61+
</p>
62+
63+
<p v-if="resource">
64+
Use modifiers to modify the value <i>before</i> it gets saved to your resource. Modifiers are combinatory
65+
meaning you can stack them together to do weird and wonderful things with your data
66+
(remember what Uncle Ben said, though!) They are executed in the order defined.
67+
</p>
68+
69+
<p>
70+
<b>TIP</b>: You can drag and drop modifiers to re-order them.
71+
</p>
6172

62-
<table cellpadding="10" v-if="resource">
63-
<thead class="border-b">
64-
<tr>
65-
<th>Field</th>
66-
<th>Value</th>
67-
<!-- <th>Modifier</th> -->
68-
</tr>
69-
</thead>
70-
<tbody>
71-
<tr v-for="field in fields[resource]">
72-
<td class="pr-2">
73-
<span class="font-bold">{{ field.name }}</span><br>
74-
<small class="text-grey-300">{{ field.attribute }}</small>
75-
</td>
76-
<td class="md:flex">
77-
<SelectControl @change="(value) => mappings[field.attribute] = value" :selected="mappings[field.attribute]">
78-
<option value="" v-if="field.rules.includes('required')" disabled>- This field is required -</option>
79-
<option value="" v-else>- Leave field empty -</option>
80-
81-
<optgroup label="File columns">
82-
<option v-for="heading in headings" :value="heading">{{ heading }}</option>
83-
</optgroup>
84-
85-
<optgroup label="Meta data">
86-
<option value="meta.file">File name (with suffix): {{ file }}</option>
87-
<option value="meta.file_name">File name (without suffix): {{ file_name }}</option>
88-
<option value="meta.original_file">Original file name (with suffix): {{ config.original_filename }}</option>
89-
<option value="meta.original_file_name">Original file name (without suffix): {{ original_file_name }}</option>
90-
</optgroup>
91-
92-
<optgroup label="Custom">
93-
<option value="custom">Single value</option>
94-
</optgroup>
95-
</SelectControl>
96-
97-
<input v-model="values[field.attribute]" v-if="mappings[field.attribute] === 'custom'"
98-
class="form-control form-input form-input-bordered ml-4">
99-
</td>
100-
</tr>
101-
</tbody>
102-
</table>
73+
<table cellpadding="10">
74+
<thead class="border-b">
75+
<tr>
76+
<th>Field</th>
77+
<th>Value</th>
78+
</tr>
79+
</thead>
80+
<tbody>
81+
<tr v-for="field in fields[resource]">
82+
<td class="pr-2">
83+
<span class="font-bold">{{ field.name }}</span><br>
84+
<small class="text-grey-300">{{ field.attribute }}</small>
85+
</td>
86+
<td class="space-y-2">
87+
<SelectControl @change="(value) => mappings[field.attribute] = value" :selected="mappings[field.attribute]">
88+
<option value="" v-if="field.rules.includes('required')" disabled>- This field is required -</option>
89+
<option value="" v-else>- Leave field empty -</option>
90+
91+
<optgroup label="File columns">
92+
<option v-for="heading in headings" :value="heading">{{ heading }}</option>
93+
</optgroup>
94+
95+
<optgroup label="Meta data">
96+
<option value="meta.file">File name (with suffix): {{ file }}</option>
97+
<option value="meta.file_name">File name (without suffix): {{ file_name }}</option>
98+
<option value="meta.original_file">Original file name (with suffix): {{ config.original_filename }}</option>
99+
<option value="meta.original_file_name">Original file name (without suffix): {{ original_file_name }}</option>
100+
</optgroup>
101+
102+
<optgroup label="Custom">
103+
<option value="custom">Single value</option>
104+
</optgroup>
105+
</SelectControl>
106+
107+
<input v-model="values[field.attribute]" v-if="mappings[field.attribute] === 'custom'"
108+
class="form-control form-input form-input-bordered">
109+
110+
<draggable
111+
v-model="modifiers[field.attribute]"
112+
handle=".handle"
113+
item-key="modifier">
114+
115+
<template #item="{ element, index }">
116+
<div class="flex mb-2 space-x-2 items-start border-rounded bg-gray-50 p-2 handle">
117+
<span>{{ index + 1 }}</span>
118+
<div class="flex flex-col flex-1 space-y-2">
119+
<SelectControl @change="(value) => element.name = value" :selected="element.name">
120+
<option value="">- Do not modify -</option>
121+
122+
<option v-for="mod in mods" :value="mod.name">{{ mod.title }}</option>
123+
</SelectControl>
124+
125+
<label v-for="(config, name) in mods[element.name].settings"
126+
v-if="mods[element.name]?.settings" class="flex items-center space-x-2"
127+
>
128+
<span>{{ config.title }}</span>
129+
130+
<SelectControl v-if="config.type === 'select'"
131+
@change="(value) => element.settings[name] = value"
132+
:selected="element.settings[name]"
133+
>
134+
<option v-for="(option, value) of config.options" :value="value"
135+
:selected="value === config.default"
136+
>
137+
{{ option }}
138+
</option>
139+
</SelectControl>
140+
141+
<input type="text" v-if="config.type === 'string'" v-model="element.settings[name]"
142+
class="form-control form-input form-input-bordered ml-4" :placeholder="config.default">
143+
144+
<input type="text" v-if="config.type === 'boolean'" v-model="element.settings[name]"
145+
class="checkbox" :checked="config.default">
146+
147+
<div class="help-text">{{ config.help }}</div>
148+
</label>
149+
</div>
150+
<button @click="removeModifier(field.attribute, index)">&times;</button>
151+
</div>
152+
</template>
153+
</draggable>
154+
155+
<button @click="addModifier(field.attribute)" v-if="mappings[field.attribute]"
156+
class="cursor-pointer rounded text-sm font-bold focus:outline-none focus:ring h-7 px-1 md:px-3"
157+
>
158+
Add modifier
159+
</button>
160+
</td>
161+
</tr>
162+
</tbody>
163+
</table>
164+
</template>
103165

104166
<div class="flex justify-center space-x-2">
105167
<LinkButton @click="goBack">
@@ -114,13 +176,19 @@
114176
</template>
115177

116178
<script>
179+
import draggable from 'vuedraggable'
180+
117181
export default {
182+
components: {
183+
draggable,
184+
},
118185
119186
data() {
120187
return {
121188
resource: this.config?.resource || '',
122189
mappings: this.config?.mappings || {},
123190
values: this.config?.values || {},
191+
modifiers: this.config?.modifiers || {},
124192
saving: false,
125193
};
126194
},
@@ -134,6 +202,7 @@ export default {
134202
'rows',
135203
'total_rows',
136204
'config',
205+
'mods',
137206
],
138207
139208
watch: {
@@ -176,6 +245,10 @@ export default {
176245
},
177246
178247
methods: {
248+
removeModifier(attribute, index) {
249+
this.modifiers[attribute].splice(index, 1);
250+
},
251+
179252
saveConfig() {
180253
if (! this.hasValidConfiguration()) {
181254
return;
@@ -187,6 +260,7 @@ export default {
187260
resource: this.resource,
188261
mappings: this.mappings,
189262
values: this.values,
263+
modifiers: this.modifiers,
190264
file: this.file,
191265
};
192266
@@ -223,9 +297,18 @@ export default {
223297
return this.resource !== '' && mappedColumns.length > 0;
224298
},
225299
226-
url: function (path) {
300+
url(path) {
227301
return '/nova-vendor/laravel-nova-csv-import/' + path;
228-
}
302+
},
303+
304+
addModifier(attribute) {
305+
if (Array.isArray(this.modifiers[attribute])) {
306+
this.modifiers[attribute].push({name: '', settings: {}});
307+
return;
308+
}
309+
310+
this.modifiers[attribute] = [{name: '', settings: {}}];
311+
},
229312
},
230313
231314
computed: {

src/Concerns/HasModifiers.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace SimonHamp\LaravelNovaCsvImport\Concerns;
4+
5+
use Illuminate\Support\Arr;
6+
use Illuminate\Support\Str;
7+
use SimonHamp\LaravelNovaCsvImport\Contracts\Modifier;
8+
use SimonHamp\LaravelNovaCsvImport\Modifiers\Boolean;
9+
use SimonHamp\LaravelNovaCsvImport\Modifiers\ExcelDate;
10+
use SimonHamp\LaravelNovaCsvImport\Modifiers\Str as StrModifier;
11+
use SimonHamp\LaravelNovaCsvImport\Modifiers\Hash;
12+
13+
trait HasModifiers
14+
{
15+
protected $modifiers = [];
16+
17+
protected static $registered_modifiers = [];
18+
19+
protected function bootHasModifiers()
20+
{
21+
// Register built-in modifiers
22+
static::registerModifiers(
23+
new Boolean,
24+
new ExcelDate,
25+
new StrModifier,
26+
new Hash,
27+
);
28+
}
29+
30+
public static function registerModifiers(Modifier ...$modifiers)
31+
{
32+
foreach ($modifiers as $modifier) {
33+
$name = Str::snake(class_basename($modifier));
34+
35+
static::$registered_modifiers[$name] = $modifier;
36+
}
37+
}
38+
39+
public function getAvailableModifiers()
40+
{
41+
return collect(static::$registered_modifiers)
42+
->map(function (Modifier $modifier, $key) {
43+
return [
44+
'name' => $key,
45+
'title' => $modifier->title(),
46+
'description' => $modifier->description(),
47+
'settings' => $this->formatModifierSettings($modifier->settings()),
48+
];
49+
})
50+
->keyBy('name');
51+
}
52+
53+
public function getModifiers($key = null): array
54+
{
55+
if ($key) {
56+
return $this->modifiers[$key] ?? [];
57+
}
58+
59+
return $this->modifiers;
60+
}
61+
62+
public function setModifiers(array $map): self
63+
{
64+
$this->modifiers = $map;
65+
66+
return $this;
67+
}
68+
69+
protected function modifyValue($value = null, array $modifiers = [])
70+
{
71+
foreach ($modifiers as $modifier) {
72+
$instance = static::$registered_modifiers[$modifier['name']];
73+
74+
$value = $instance->handle($value, $modifier['settings'] ?? []);
75+
}
76+
77+
78+
return $value;
79+
}
80+
81+
protected function formatModifierSettings(array $settings = [])
82+
{
83+
$normalised_settings = [];
84+
foreach ($settings as $name => $setting) {
85+
$normalised = [
86+
'title' => $setting['title'] ?? Str::title($name),
87+
'type' => is_string($setting) ? 'string' : $setting['type'] ?? 'select',
88+
'default' => $setting['default'] ?? '',
89+
];
90+
91+
if ($normalised['type'] === 'select') {
92+
$options = $setting['options'] ?? $setting;
93+
94+
$normalised['options'] = Arr::isAssoc($options) ? $options : array_combine($options, $options);
95+
}
96+
97+
$normalised_settings[$name] = $normalised;
98+
}
99+
100+
return $normalised_settings;
101+
}
102+
}

src/Contracts/Modifier.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace SimonHamp\LaravelNovaCsvImport\Contracts;
4+
5+
interface Modifier
6+
{
7+
public function title(): string;
8+
9+
public function description(): string;
10+
11+
public function settings(): array;
12+
13+
public function handle($value = null, array $settings = []);
14+
}

0 commit comments

Comments
 (0)