Skip to content

Commit

Permalink
Add Modifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
simonhamp committed Apr 27, 2022
1 parent 21e5f63 commit 721d154
Show file tree
Hide file tree
Showing 12 changed files with 445 additions and 53 deletions.
3 changes: 2 additions & 1 deletion dist/js/tool.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions dist/js/tool.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**!
* Sortable 1.14.0
* @author RubaXa <[email protected]>
* @author owenm <[email protected]>
* @license MIT
*/
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
"postcss": "^8.3.11",
"vue-loader": "^16.8.3"
},
"dependencies": {}
"dependencies": {
"vuedraggable": "^4.1.0"
}
}
177 changes: 130 additions & 47 deletions resources/js/pages/Configure.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,52 +54,114 @@
</card>

<card class="p-8 space-y-4">
<p v-if="resource">
Choose which data to fill the appropriate fields of the chosen resource. The columns from your uploaded
file have been auto-matched to the resource fields with the same name.
</p>
<template v-if="resource">
<p>
Choose which data to fill the appropriate fields of the chosen resource. The columns from your uploaded
file have been auto-matched to the resource fields with the same name.
</p>

<p v-if="resource">
Use modifiers to modify the value <i>before</i> it gets saved to your resource. Modifiers are combinatory
meaning you can stack them together to do weird and wonderful things with your data
(remember what Uncle Ben said, though!) They are executed in the order defined.
</p>

<p>
<b>TIP</b>: You can drag and drop modifiers to re-order them.
</p>

<table cellpadding="10" v-if="resource">
<thead class="border-b">
<tr>
<th>Field</th>
<th>Value</th>
<!-- <th>Modifier</th> -->
</tr>
</thead>
<tbody>
<tr v-for="field in fields[resource]">
<td class="pr-2">
<span class="font-bold">{{ field.name }}</span><br>
<small class="text-grey-300">{{ field.attribute }}</small>
</td>
<td class="md:flex">
<SelectControl @change="(value) => mappings[field.attribute] = value" :selected="mappings[field.attribute]">
<option value="" v-if="field.rules.includes('required')" disabled>- This field is required -</option>
<option value="" v-else>- Leave field empty -</option>

<optgroup label="File columns">
<option v-for="heading in headings" :value="heading">{{ heading }}</option>
</optgroup>

<optgroup label="Meta data">
<option value="meta.file">File name (with suffix): {{ file }}</option>
<option value="meta.file_name">File name (without suffix): {{ file_name }}</option>
<option value="meta.original_file">Original file name (with suffix): {{ config.original_filename }}</option>
<option value="meta.original_file_name">Original file name (without suffix): {{ original_file_name }}</option>
</optgroup>

<optgroup label="Custom">
<option value="custom">Single value</option>
</optgroup>
</SelectControl>

<input v-model="values[field.attribute]" v-if="mappings[field.attribute] === 'custom'"
class="form-control form-input form-input-bordered ml-4">
</td>
</tr>
</tbody>
</table>
<table cellpadding="10">
<thead class="border-b">
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr v-for="field in fields[resource]">
<td class="pr-2">
<span class="font-bold">{{ field.name }}</span><br>
<small class="text-grey-300">{{ field.attribute }}</small>
</td>
<td class="space-y-2">
<SelectControl @change="(value) => mappings[field.attribute] = value" :selected="mappings[field.attribute]">
<option value="" v-if="field.rules.includes('required')" disabled>- This field is required -</option>
<option value="" v-else>- Leave field empty -</option>

<optgroup label="File columns">
<option v-for="heading in headings" :value="heading">{{ heading }}</option>
</optgroup>

<optgroup label="Meta data">
<option value="meta.file">File name (with suffix): {{ file }}</option>
<option value="meta.file_name">File name (without suffix): {{ file_name }}</option>
<option value="meta.original_file">Original file name (with suffix): {{ config.original_filename }}</option>
<option value="meta.original_file_name">Original file name (without suffix): {{ original_file_name }}</option>
</optgroup>

<optgroup label="Custom">
<option value="custom">Single value</option>
</optgroup>
</SelectControl>

<input v-model="values[field.attribute]" v-if="mappings[field.attribute] === 'custom'"
class="form-control form-input form-input-bordered">

<draggable
v-model="modifiers[field.attribute]"
handle=".handle"
item-key="modifier">

<template #item="{ element, index }">
<div class="flex mb-2 space-x-2 items-start border-rounded bg-gray-50 p-2 handle">
<span>{{ index + 1 }}</span>
<div class="flex flex-col flex-1 space-y-2">
<SelectControl @change="(value) => element.name = value" :selected="element.name">
<option value="">- Do not modify -</option>

<option v-for="mod in mods" :value="mod.name">{{ mod.title }}</option>
</SelectControl>

<label v-for="(config, name) in mods[element.name].settings"
v-if="mods[element.name]?.settings" class="flex items-center space-x-2"
>
<span>{{ config.title }}</span>
<SelectControl v-if="config.type === 'select'"
@change="(value) => element.settings[name] = value"
:selected="element.settings[name]"
>
<option v-for="(option, value) of config.options" :value="value"
:selected="value === config.default"
>
{{ option }}
</option>
</SelectControl>
<input type="text" v-if="config.type === 'string'" v-model="element.settings[name]"
class="form-control form-input form-input-bordered ml-4" :placeholder="config.default">
<input type="text" v-if="config.type === 'boolean'" v-model="element.settings[name]"
class="checkbox" :checked="config.default">
<div class="help-text">{{ config.help }}</div>
</label>
</div>
<button @click="removeModifier(field.attribute, index)">&times;</button>
</div>
</template>
</draggable>
<button @click="addModifier(field.attribute)" v-if="mappings[field.attribute]"
class="cursor-pointer rounded text-sm font-bold focus:outline-none focus:ring h-7 px-1 md:px-3"
>
Add modifier
</button>
</td>
</tr>
</tbody>
</table>
</template>

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

<script>
import draggable from 'vuedraggable'
export default {
components: {
draggable,
},
data() {
return {
resource: this.config?.resource || '',
mappings: this.config?.mappings || {},
values: this.config?.values || {},
modifiers: this.config?.modifiers || {},
saving: false,
};
},
Expand All @@ -134,6 +202,7 @@ export default {
'rows',
'total_rows',
'config',
'mods',
],
watch: {
Expand Down Expand Up @@ -176,6 +245,10 @@ export default {
},
methods: {
removeModifier(attribute, index) {
this.modifiers[attribute].splice(index, 1);
},
saveConfig() {
if (! this.hasValidConfiguration()) {
return;
Expand All @@ -187,6 +260,7 @@ export default {
resource: this.resource,
mappings: this.mappings,
values: this.values,
modifiers: this.modifiers,
file: this.file,
};
Expand Down Expand Up @@ -223,9 +297,18 @@ export default {
return this.resource !== '' && mappedColumns.length > 0;
},
url: function (path) {
url(path) {
return '/nova-vendor/laravel-nova-csv-import/' + path;
}
},
addModifier(attribute) {
if (Array.isArray(this.modifiers[attribute])) {
this.modifiers[attribute].push({name: '', settings: {}});
return;
}
this.modifiers[attribute] = [{name: '', settings: {}}];
},
},
computed: {
Expand Down
102 changes: 102 additions & 0 deletions src/Concerns/HasModifiers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace SimonHamp\LaravelNovaCsvImport\Concerns;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use SimonHamp\LaravelNovaCsvImport\Contracts\Modifier;
use SimonHamp\LaravelNovaCsvImport\Modifiers\Boolean;
use SimonHamp\LaravelNovaCsvImport\Modifiers\ExcelDate;
use SimonHamp\LaravelNovaCsvImport\Modifiers\Str as StrModifier;
use SimonHamp\LaravelNovaCsvImport\Modifiers\Hash;

trait HasModifiers
{
protected $modifiers = [];

protected static $registered_modifiers = [];

protected function bootHasModifiers()
{
// Register built-in modifiers
static::registerModifiers(
new Boolean,
new ExcelDate,
new StrModifier,
new Hash,
);
}

public static function registerModifiers(Modifier ...$modifiers)
{
foreach ($modifiers as $modifier) {
$name = Str::snake(class_basename($modifier));

static::$registered_modifiers[$name] = $modifier;
}
}

public function getAvailableModifiers()
{
return collect(static::$registered_modifiers)
->map(function (Modifier $modifier, $key) {
return [
'name' => $key,
'title' => $modifier->title(),
'description' => $modifier->description(),
'settings' => $this->formatModifierSettings($modifier->settings()),
];
})
->keyBy('name');
}

public function getModifiers($key = null): array
{
if ($key) {
return $this->modifiers[$key] ?? [];
}

return $this->modifiers;
}

public function setModifiers(array $map): self
{
$this->modifiers = $map;

return $this;
}

protected function modifyValue($value = null, array $modifiers = [])
{
foreach ($modifiers as $modifier) {
$instance = static::$registered_modifiers[$modifier['name']];

$value = $instance->handle($value, $modifier['settings'] ?? []);
}


return $value;
}

protected function formatModifierSettings(array $settings = [])
{
$normalised_settings = [];
foreach ($settings as $name => $setting) {
$normalised = [
'title' => $setting['title'] ?? Str::title($name),
'type' => is_string($setting) ? 'string' : $setting['type'] ?? 'select',
'default' => $setting['default'] ?? '',
];

if ($normalised['type'] === 'select') {
$options = $setting['options'] ?? $setting;

$normalised['options'] = Arr::isAssoc($options) ? $options : array_combine($options, $options);
}

$normalised_settings[$name] = $normalised;
}

return $normalised_settings;
}
}
14 changes: 14 additions & 0 deletions src/Contracts/Modifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace SimonHamp\LaravelNovaCsvImport\Contracts;

interface Modifier
{
public function title(): string;

public function description(): string;

public function settings(): array;

public function handle($value = null, array $settings = []);
}
Loading

0 comments on commit 721d154

Please sign in to comment.