diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..72f3b85 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +.PHONY: build dev watch prod ana + +build: dev + +dev: + npm run dev +watch: + npm run watch +ana: + npm run dev-analyze +prod: + npm run prod diff --git a/_json_typehints.php b/_json_typehints.php index 7021d45..6565ac6 100644 --- a/_json_typehints.php +++ b/_json_typehints.php @@ -10,6 +10,7 @@ interface RowData {} /** * Interface DecoratedRow * + * @property bool $_new - row is new in the changeset * @property bool $_remove - marked to be removed * @property mixed[] $_orig - original values before transformation, key by CID * @property string[] $_changed - values that were changed diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index a19a7b0..383db2b 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -56,7 +56,7 @@ class Controller extends BaseController 'mastodon', // mastodon fetching previews ]; - protected function jsonResponse($data = [], $code=200) + protected function jsonResponse($data, $code=200) { return new JsonResponse($data, $code); } diff --git a/app/Http/Controllers/TableController.php b/app/Http/Controllers/TableController.php index 89acd42..bdfa575 100644 --- a/app/Http/Controllers/TableController.php +++ b/app/Http/Controllers/TableController.php @@ -207,39 +207,13 @@ class TableController extends Controller } // --- DATA --- - $dataCsvLines = array_map('str_getcsv', explode("\n", $input->data)); + $dataCsvLines = Utils::csvToArray($input->data); // Preparing data to insert into the Rows table $rowsToInsert = null; $rowNumerator = null; try { - $rowsToInsert = collect($dataCsvLines)->map(function ($row) use ($columns) { - if (count($row) == 0 || count($row) == 1 && $row[0] == '') return null; // discard empty lines - if (count($row) != count($columns)) { - throw new NotApplicableException("All rows must have " . count($columns) . " fields."); - } - - $data = []; - - foreach ($row as $i => $val) { - $col = $columns[$i]; - - if (strlen($val) > 1000) { - // try to stop people inserting unstructured crap / malformed CSV - throw new NotApplicableException("Value for column {$col->name} too long."); - } - $data[$col->id] = $col->cast($val); - } - - // return in a format prepared for filling eloquent - return ['data' => $data]; - })->filter()->all(); // remove nulls, to array - - // attach _id labels to all rows - $rowNumerator = new RowNumerator(count($dataCsvLines)); - foreach ($rowsToInsert as &$item) { - $item['data']['_id'] = $rowNumerator->next(); - } + $rowsToInsert = Changeset::csvToRowsArray($columns, $dataCsvLines, true)->all(); } catch (\Exception $e) { return $this->backWithErrors(['data' => $e->getMessage()]); } diff --git a/app/Http/Controllers/TableEditController.php b/app/Http/Controllers/TableEditController.php index 16ef366..db55166 100644 --- a/app/Http/Controllers/TableEditController.php +++ b/app/Http/Controllers/TableEditController.php @@ -9,6 +9,7 @@ use App\Tables\Changeset; use Illuminate\Http\Request; use Illuminate\Support\Facades\Input; use MightyPork\Exceptions\SimpleValidationException; +use MightyPork\Utils\Utils; class TableEditController extends Controller { @@ -100,7 +101,7 @@ class TableEditController extends Controller ]); } - public function draftUpdate(User $user, string $table, Request $request) + public function draftUpdate(Request $request, User $user, string $table) { /** @var Table $tableModel */ $tableModel = $user->tables()->where('name', $table)->first(); @@ -121,8 +122,9 @@ class TableEditController extends Controller break; case 'row.remove': + $isNew = $changeset->isNewRow($input->id); $changeset->rowRemove($input->id); - $resp = $changeset->fetchAndTransformRow($input->id); + $resp = $isNew ? null : $changeset->fetchAndTransformRow($input->id); break; case 'row.restore': @@ -130,6 +132,32 @@ class TableEditController extends Controller $resp = $changeset->fetchAndTransformRow($input->id); break; + case 'rows.add': + $changeset->addBlankRows($input->count); + + // rows.add is sent via a form + if ($input->has('redirect')) { + return redirect($input->redirect); + } else { + $resp = null; + } + break; + + case 'rows.add-csv': + try { + $changeset->addFilledRows(Utils::csvToArray($input->data)); + } catch (\Exception $e) { + return $this->backWithErrors(['data' => $e->getMessage()]); + } + + // rows.add-csv is sent via a form + if ($input->has('redirect')) { + return redirect($input->redirect); + } else { + $resp = null; + } + break; + default: $resp = "Bad Action"; $code = 400; diff --git a/app/Tables/BaseNumerator.php b/app/Tables/BaseNumerator.php new file mode 100644 index 0000000..e94cb41 --- /dev/null +++ b/app/Tables/BaseNumerator.php @@ -0,0 +1,76 @@ +next = $first; + $this->last = $last; + } + + /** + * Get next key, incrementing the internal state + * + * @return string + */ + public function next() + { + if (!$this->hasMore()) + throw new \OutOfBoundsException("Column numerator has run out of allocated GCID slots"); + + $key = $this->getKey($this->next); + $this->next++; + return $key; + } + + /** + * Convert numeric index to a key + * + * @param int $index + * @return mixed + */ + protected function getKey($index) + { + return $index; // simple default + } + + /** + * @return bool - true iof there are more keys available + */ + protected function hasMore() + { + return $this->next <= $this->last; + } + + /** + * Generate all keys + * + * @return \Generator + */ + public function generate() + { + while ($this->hasMore()) { + yield $this->next(); + } + } +} diff --git a/app/Tables/Changeset.php b/app/Tables/Changeset.php index 8ba6d96..7e4fbdf 100644 --- a/app/Tables/Changeset.php +++ b/app/Tables/Changeset.php @@ -9,6 +9,7 @@ use App\Models\Table; use Illuminate\Pagination\Paginator; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; +use MightyPork\Exceptions\NotApplicableException; use MightyPork\Exceptions\NotExistException; use ReflectionClass; @@ -54,7 +55,7 @@ class Changeset * New rows in the full Row::data format, including GRIDs. * Values are identified by GCIDs from previously defined, or new columns. * - * @var array|null - [[_id:…, cid:…, cid:…], …] + * @var array|null - [_id -> [_id:…, cid:…, cid:…], …] */ public $newRows = []; @@ -78,7 +79,7 @@ class Changeset /** * New columns in the full format, including GCIDs * - * @var array|null - [[id:…, …], …] + * @var array|null - [id -> [id:…, …], …] */ public $newColumns = []; @@ -187,6 +188,13 @@ class Changeset $row->_orig = []; } + if ($this->isNewRow($row->_id)) { + if ($decorate) { + $row->_new = true; + } + return $row; + } + // Removed rows - return as null if (in_array($row->_id, $this->removedRows)) { if ($decorate) { @@ -249,7 +257,7 @@ class Changeset // Append new columns foreach ($this->newColumns as $newColumn) { - $columns[] = $c =new Column($newColumn); + $columns[] = $c = new Column($newColumn); $c->markAsNew(); } @@ -270,7 +278,12 @@ class Changeset public function rowRemove(int $id) { - $this->removedRows[] = $id; + if ($this->isNewRow($id)) { + unset($this->newRows[$id]); + } + else { + $this->removedRows[] = $id; + } } public function rowRestore(int $id) @@ -278,6 +291,11 @@ class Changeset $this->removedRows = array_diff($this->removedRows, [$id]); } + public function isNewRow(int $id) + { + return isset($this->newRows[$id]); + } + public function fetchAndTransformRow(int $id) { $r = $this->fetchRow($id); @@ -290,12 +308,22 @@ class Changeset return Column::columnsFromJson($this->revision->columns); } - public function fetchRow($id) + /** + * Fetch an existing row from DB, or a new row. + * + * @param $id + * @return object + */ + public function fetchRow(int $id) { + if ($this->isNewRow($id)) { + return (object)$this->newRows[$id]; + } + $r = $this->revision->rowsData($this->fetchColumns(), true, false) ->whereRaw("data->>'_id' = ?", $id)->first(); - if (! $r) throw new NotExistException("No such row _id = $id in this revision."); + if (!$r) throw new NotExistException("No such row _id = $id in this revision."); // remove junk unset($r->pivot_revision_id); @@ -308,7 +336,7 @@ class Changeset * Apply a row update, adding the row to the list of changes, or removing it * if all differences were undone. * - * @param array|object $newVals + * @param array|object $newVals - values of the new row */ public function rowUpdate($newVals) { @@ -334,11 +362,16 @@ class Changeset } } - if (!empty($updateObj)) { - $this->rowUpdates[$_id] = $updateObj; - } else { - // remove possible old update record for this row, if nothing changes now - unset($this->rowUpdates[$_id]); + if ($this->isNewRow($_id)) { + $this->newRows[$_id] = array_merge($this->newRows[$_id], $updateObj); + } + else { + if (!empty($updateObj)) { + $this->rowUpdates[$_id] = $updateObj; + } else { + // remove possible old update record for this row, if nothing changes now + unset($this->rowUpdates[$_id]); + } } } @@ -348,8 +381,87 @@ class Changeset * @param int $perPage * @return \Illuminate\Pagination\LengthAwarePaginator|Collection|array */ - public function getAddedRows(int $perPage = 25) + public function getAddedRows($perPage = 25) { return collection_paginate($this->newRows, $perPage); } + + /** + * @param Column[] $columns + * @param array $csvArray + * @param bool $forTableInsert + * @return \array[][]|Collection + */ + public static function csvToRowsArray($columns, $csvArray, $forTableInsert) + { + /** @var Collection|array[][] $rows */ + $rows = collect($csvArray)->map(function ($row) use ($columns, $forTableInsert) { + if (count($row) == 0 || count($row) == 1 && $row[0] == '') return null; // discard empty lines + if (count($row) != count($columns)) { + throw new NotApplicableException("All rows must have " . count($columns) . " fields."); + } + + $data = []; + + foreach ($row as $i => $val) { + $col = $columns[$i]; + + if (strlen($val) > 1000) { + // try to stop people inserting unstructured crap / malformed CSV + throw new NotApplicableException("Value for column {$col->name} too long."); + } + $data[$col->id] = $col->cast($val); + } + + if ($forTableInsert) { + return ['data' => $data]; + } else { + return $data; + } + })->filter(); + + + $rowNumerator = new RowNumerator(count($csvArray)); + + if ($forTableInsert) { + return $rows->map(function ($row) use (&$rowNumerator) { + $row['data']['_id'] = $rowNumerator->next(); + return $row; + }); + } + else { + return $rows->map(function ($row) use (&$rowNumerator) { + $row['_id'] = $rowNumerator->next(); + return $row; + }); + } + } + + public function addBlankRows(int $count) + { + $numerator = new RowNumerator($count); + + $columns = $this->fetchAndTransformColumns(); + $template = []; + foreach ($columns as $column) { + $template[$column->id] = $column->cast(null); + } + + foreach ($numerator->generate() as $_id) { + $row = $template; + $row['_id'] = $_id; + $this->newRows[$_id] = $row; + } + } + + public function addFilledRows($csvArray) + { + /** @var Column[] $columns */ + $columns = array_values($this->fetchAndTransformColumns()); + + $rows = self::csvToRowsArray($columns, $csvArray, false) + ->keyBy('_id'); + + $this->newRows = array_merge($this->newRows, $rows->all()); + } } diff --git a/app/Tables/Column.php b/app/Tables/Column.php index d0fb9e4..4c24329 100644 --- a/app/Tables/Column.php +++ b/app/Tables/Column.php @@ -129,7 +129,7 @@ class Column implements JsonSerializable, Arrayable { $b = objBag($obj); - if (!$b->has('name')) throw new NotApplicableException('Missing name in column'); + if (!$b->has('name') || $b->name == '') throw new NotApplicableException('Missing name in column'); if (!$b->has('type')) throw new NotApplicableException('Missing type in column'); if ($b->name[0] == '_') { // global row ID diff --git a/app/Tables/ColumnNumerator.php b/app/Tables/ColumnNumerator.php index e053192..bde16fd 100644 --- a/app/Tables/ColumnNumerator.php +++ b/app/Tables/ColumnNumerator.php @@ -17,36 +17,23 @@ use MightyPork\Utils\Utils; * Thanks to this uniqueness, it could even be possible to compare or merge forks * of the same table. */ -class ColumnNumerator +class ColumnNumerator extends BaseNumerator { const ALPHABET = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; - /** @var int */ - private $next; - /** @var int */ - private $last; - /** * Create numerator for the given number of columns + * + * @param int $capacity - how many */ public function __construct($capacity) { - list($this->next, $this->last) = Row::allocateColIDs($capacity); + parent::__construct(Row::allocateColIDs($capacity)); } - /** - * Get next column name, incrementing the internal state - * - * @return string - */ - public function next() + protected function getKey($index) { - if ($this->next > $this->last) - throw new \OutOfBoundsException("Column numerator has run out of allocated GCID slots"); - - $key = Utils::alphabetEncode($this->next, self::ALPHABET); - $this->next++; - return $key; + return Utils::alphabetEncode($index, self::ALPHABET); } } diff --git a/app/Tables/RowNumerator.php b/app/Tables/RowNumerator.php index dde4484..c493f1b 100644 --- a/app/Tables/RowNumerator.php +++ b/app/Tables/RowNumerator.php @@ -5,28 +5,15 @@ namespace App\Tables; use App\Models\Row; -class RowNumerator +class RowNumerator extends BaseNumerator { - /** @var int */ - private $next; - /** @var int */ - private $last; - /** * Create a numerator for the given number of rows. * - * @param int $capacity + * @param int $capacity - how many */ public function __construct($capacity) { - list($this->next, $this->last) = Row::allocateRowIDs($capacity); - } - - public function next() - { - if ($this->next > $this->last) - throw new \OutOfBoundsException("Row numerator has run out of allocated GRID slots"); - - return $this->next++; + parent::__construct(Row::allocateRowIDs($capacity)); } } diff --git a/app/helpers.php b/app/helpers.php index b07fe11..789d6e0 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -84,7 +84,13 @@ function old_json($name, $default) { } // Safe JSON funcs -function toJSON($object) { +function toJSON($object, $emptyObj=false) { + if ($emptyObj) { + if ((empty($object) || ($object instanceof \Illuminate\Support\Collection && $object->count()==0))) { + return '{}'; + } + } + if (!$object instanceof JsonSerializable && $object instanceof \Illuminate\Contracts\Support\Arrayable) { $object = $object->toArray(); } diff --git a/config/app.php b/config/app.php index 692b7d2..7f8021b 100644 --- a/config/app.php +++ b/config/app.php @@ -215,6 +215,7 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, + 'Input' => \Illuminate\Support\Facades\Input::class, // sideload 'SocialAuth' => AdamWathan\EloquentOAuth\Facades\OAuth::class, diff --git a/config/debugbar.php b/config/debugbar.php new file mode 100644 index 0000000..8350e1c --- /dev/null +++ b/config/debugbar.php @@ -0,0 +1,201 @@ + env('DEBUGBAR_ENABLED', null), + 'except' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Storage settings + |-------------------------------------------------------------------------- + | + | DebugBar stores data for session/ajax requests. + | You can disable this, so the debugbar stores data in headers/session, + | but this can cause problems with large data collectors. + | By default, file storage (in the storage folder) is used. Redis and PDO + | can also be used. For PDO, run the package migrations first. + | + */ + 'storage' => [ + 'enabled' => true, + 'driver' => 'file', // redis, file, pdo, custom + 'path' => storage_path('debugbar'), // For file driver + 'connection' => null, // Leave null for default connection (Redis/PDO) + 'provider' => '' // Instance of StorageInterface for custom driver + ], + + /* + |-------------------------------------------------------------------------- + | Vendors + |-------------------------------------------------------------------------- + | + | Vendor files are included by default, but can be set to false. + | This can also be set to 'js' or 'css', to only include javascript or css vendor files. + | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) + | and for js: jquery and and highlight.js + | So if you want syntax highlighting, set it to true. + | jQuery is set to not conflict with existing jQuery scripts. + | + */ + + 'include_vendors' => true, + + /* + |-------------------------------------------------------------------------- + | Capture Ajax Requests + |-------------------------------------------------------------------------- + | + | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors), + | you can use this option to disable sending the data through the headers. + | + | Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools. + */ + + 'capture_ajax' => true, + 'add_ajax_timing' => false, + + /* + |-------------------------------------------------------------------------- + | Custom Error Handler for Deprecated warnings + |-------------------------------------------------------------------------- + | + | When enabled, the Debugbar shows deprecated warnings for Symfony components + | in the Messages tab. + | + */ + 'error_handler' => false, + + /* + |-------------------------------------------------------------------------- + | Clockwork integration + |-------------------------------------------------------------------------- + | + | The Debugbar can emulate the Clockwork headers, so you can use the Chrome + | Extension, without the server-side code. It uses Debugbar collectors instead. + | + */ + 'clockwork' => false, + + /* + |-------------------------------------------------------------------------- + | DataCollectors + |-------------------------------------------------------------------------- + | + | Enable/disable DataCollectors + | + */ + + 'collectors' => [ + 'phpinfo' => true, // Php version + 'messages' => true, // Messages + 'time' => true, // Time Datalogger + 'memory' => true, // Memory usage + 'exceptions' => true, // Exception displayer + 'log' => true, // Logs from Monolog (merged in messages if enabled) + 'db' => true, // Show database (PDO) queries and bindings + 'views' => true, // Views with their data + 'route' => true, // Current route information + 'auth' => true, // Display Laravel authentication status + 'gate' => true, // Display Laravel Gate checks + 'session' => true, // Display session data + 'symfony_request' => true, // Only one can be enabled.. + 'mail' => true, // Catch mail messages + 'laravel' => false, // Laravel version and environment + 'events' => false, // All events fired + 'default_request' => false, // Regular or special Symfony request logger + 'logs' => false, // Add the latest log messages + 'files' => false, // Show the included files + 'config' => false, // Display config settings + 'cache' => false, // Display cache events + ], + + /* + |-------------------------------------------------------------------------- + | Extra options + |-------------------------------------------------------------------------- + | + | Configure some DataCollectors + | + */ + + 'options' => [ + 'auth' => [ + 'show_name' => true, // Also show the users name/email in the debugbar + ], + 'db' => [ + 'with_params' => true, // Render SQL with the parameters substituted + 'backtrace' => true, // Use a backtrace to find the origin of the query in your files. + 'timeline' => false, // Add the queries to the timeline + 'explain' => [ // Show EXPLAIN output on queries + 'enabled' => false, + 'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+ + ], + 'hints' => true, // Show hints for common mistakes + ], + 'mail' => [ + 'full_log' => false + ], + 'views' => [ + 'data' => false, //Note: Can slow down the application, because the data can be quite large.. + ], + 'route' => [ + 'label' => true // show complete route on bar + ], + 'logs' => [ + 'file' => null + ], + 'cache' => [ + 'values' => true // collect cache values + ], + ], + + /* + |-------------------------------------------------------------------------- + | Inject Debugbar in Response + |-------------------------------------------------------------------------- + | + | Usually, the debugbar is added just before , by listening to the + | Response after the App is done. If you disable this, you have to add them + | in your template yourself. See http://phpdebugbar.com/docs/rendering.html + | + */ + + 'inject' => true, + + /* + |-------------------------------------------------------------------------- + | DebugBar route prefix + |-------------------------------------------------------------------------- + | + | Sometimes you want to set route prefix to be used by DebugBar to load + | its resources from. Usually the need comes from misconfigured web server or + | from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 + | + */ + 'route_prefix' => '_debugbar', + + /* + |-------------------------------------------------------------------------- + | DebugBar route domain + |-------------------------------------------------------------------------- + | + | By default DebugBar route served from the same domain that request served. + | To override default domain, specify it as a non-empty value. + */ + 'route_domain' => null, +]; diff --git a/package.json b/package.json index 4b16bd0..172d403 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "private": true, "scripts": { "dev": "npm run development", + "dev-analyze": "WP_ANALYZE=1 npm run development", "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "watch": "npm run development -- --watch", "watch-poll": "npm run watch -- --watch-poll", diff --git a/porklib/Utils/Utils.php b/porklib/Utils/Utils.php index 6e1e97d..7b2789f 100644 --- a/porklib/Utils/Utils.php +++ b/porklib/Utils/Utils.php @@ -873,7 +873,7 @@ class Utils * @param int|null $timestamp formatted timestamp, or null for current time * @return string result */ - public static function fdate($format, $timestamp = null) + public static function fdate(string $format, $timestamp = null) { if ($timestamp === null) $timestamp = time(); @@ -889,7 +889,7 @@ class Utils * @param bool $rough get only approximate time (for estimate) * @return string result */ - public static function ftime($secs, $rough = false) + public static function ftime(int $secs, $rough = false) { $d = (int) ($secs / 86400); $secs -= $d * 86400; @@ -946,7 +946,7 @@ class Utils * @param $time * @return int seconds */ - public static function strToSeconds($time) + public static function strToSeconds(string $time) { // seconds pass through if (preg_match('/^\d+$/', trim("$time"))) { @@ -1013,4 +1013,9 @@ class Utils return $key; } + + public static function csvToArray(string $data) + { + return array_map('str_getcsv', explode("\n", $data)); + } } diff --git a/resources/assets/js/components/RowEditor.vue b/resources/assets/js/components/RowsEditor.vue similarity index 85% rename from resources/assets/js/components/RowEditor.vue rename to resources/assets/js/components/RowsEditor.vue index 903479d..4e8b846 100644 --- a/resources/assets/js/components/RowEditor.vue +++ b/resources/assets/js/components/RowsEditor.vue @@ -4,7 +4,7 @@ - {{col.title}} + {{col.title}} @@ -59,6 +59,7 @@ export default { route: String, xRows: Object, // key'd by _id columns: Array, + lastPage: Boolean, }, data: function() { return { @@ -83,13 +84,22 @@ export default { }, toggleRowDelete(_id) { + if (!_.isDefined(this.rows[_id])) return; + let remove = !this.rows[_id]._remove this.query({ action: remove ? 'row.remove' : 'row.restore', id: _id }, (resp) => { - this.$set(this.rows, _id, resp.data) + // if response is null, this was a New row + // and it was discarded without a way back - hard drop + if (_.isEmpty(resp.data)) { + this.$delete(this.rows, _id) + } + else { + this.$set(this.rows, _id, resp.data) + } }) }, @@ -131,13 +141,15 @@ export default { rowStyle(row) { return { - opacity: row._remove? .8 : 1, - backgroundColor: row._remove? '#FFC4CC': 'transparent' + opacity: row._remove ? .8 : 1, + backgroundColor: + row._remove ? '#FFC4CC': + 'transparent' } }, isChanged (row, colId) { - return row._changed.indexOf(colId) > -1 + return row._changed && row._changed.indexOf(colId) > -1 }, revertCell(row, colId) { diff --git a/resources/assets/js/udash.js b/resources/assets/js/udash.js index 3cc8986..747b133 100644 --- a/resources/assets/js/udash.js +++ b/resources/assets/js/udash.js @@ -1,5 +1,12 @@ // subset of used lodash modules -export { default as each } from 'lodash/each'; -export { default as isUndefined } from 'lodash/isUndefined'; -export { default as merge } from 'lodash/merge'; +export { default as each } from 'lodash/each' +export { default as isUndefined } from 'lodash/isUndefined' +export { default as merge } from 'lodash/merge' +export { default as unset } from 'lodash/unset' +export { default as isEmpty } from 'lodash/isEmpty' + +function isDefined(x) { + return typeof(x) !== 'undefined'; +} +export { isDefined } diff --git a/resources/assets/js/vue-init.js b/resources/assets/js/vue-init.js index 5b51bf6..5543a57 100644 --- a/resources/assets/js/vue-init.js +++ b/resources/assets/js/vue-init.js @@ -1,9 +1,27 @@ window.Vue = require('vue'); -Vue.component('column-editor', require('./components/ColumnEditor.vue')); -Vue.component('row-editor', require('./components/RowEditor.vue')); -Vue.component('v-icon', require('./components/Icon.vue')); +const ColumnEditorCtor = Vue.component('column-editor', require('./components/ColumnEditor.vue')); +const RowsEditorCtor = Vue.component('rows-editor', require('./components/RowsEditor.vue')); +const IconCtor = Vue.component('v-icon', require('./components/Icon.vue')); -const app = new Vue({ - el: '#app' -}); +// const app = new Vue({ +// el: '#app' +// }); + +window.app = { + ColumnEditor: function(selector, data) { + new ColumnEditorCtor({ + propsData: data + }).$mount(selector); + }, + RowsEditor: function(selector, data) { + new RowsEditorCtor({ + propsData: data + }).$mount(selector); + }, + Icon: function(selector, data) { + new IconCtor({ + propsData: data + }).$mount(selector); + } +} diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index bd7b9e5..bbecb81 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -12,6 +12,12 @@ + + @stack('scripts') diff --git a/resources/views/table/_rows.blade.php b/resources/views/table/_rows.blade.php index 3a59768..7c6895e 100644 --- a/resources/views/table/_rows.blade.php +++ b/resources/views/table/_rows.blade.php @@ -11,14 +11,16 @@ + @foreach($columns as $col) - + @endforeach @foreach($rows as $row) + @foreach($columns as $col) @endforeach diff --git a/resources/views/table/create.blade.php b/resources/views/table/create.blade.php index 743603f..1364f06 100644 --- a/resources/views/table/create.blade.php +++ b/resources/views/table/create.blade.php @@ -30,23 +30,12 @@ ->help('If you took the data from some external site, a book, etc., write it here. URLs in a full format will be clickable.') !!} - {{--!! Widget::textarea('columns', 'Columns')->value($exampleColumns)->height('8em') - ->help(' -
- Column parameters in CSV format: - -
') !!--}} -
- +
@endsection + +@push('scripts') + +@endpush diff --git a/resources/views/table/propose/add-rows.blade.php b/resources/views/table/propose/add-rows.blade.php index 157dd53..84bf27b 100644 --- a/resources/views/table/propose/add-rows.blade.php +++ b/resources/views/table/propose/add-rows.blade.php @@ -1,6 +1,52 @@ -@php($tab='add-rows') -@extends('table.propose.layout') +@php + $tab = 'add-rows'; + /** @var \App\Tables\Column[] $columns */ + /** @var \App\Tables\Changeset $changeset */ + /** @var \App\Models\Row[]|Illuminate\Pagination\Paginator $rows */ + /** @var \App\Models\Table $table */ +@endphp +@extends('table.propose.layout-row-pagination') -@section('tab-content') - ... +@section('header') +
+ {{-- TODO improve layout --}} +
+ @csrf + + + + + + + +
+ +
+ @csrf + + + + + + +
@stop + +@section('rows') +
+@stop + +@push('scripts') + +@endpush diff --git a/resources/views/table/propose/edit-rows.blade.php b/resources/views/table/propose/edit-rows.blade.php index a59b866..794aa13 100644 --- a/resources/views/table/propose/edit-rows.blade.php +++ b/resources/views/table/propose/edit-rows.blade.php @@ -4,9 +4,6 @@ /** @var \App\Tables\Changeset $changeset */ /** @var \App\Models\Row[]|Illuminate\Pagination\Paginator $rows */ /** @var \App\Models\Table $table */ - /** @var \App\Tables\Column[] $columns */ - /** @var \App\Tables\Changeset $changeset */ - /** @var \App\Models\Table $table */ @endphp @extends('table.propose.layout-row-pagination') @@ -19,9 +16,17 @@ }); @endphp -
#{{ $col->title }}{{ $col->title }}
{{$row->_id}}{{ $row->{$col->name} }}
-
+
@stop + +@push('scripts') + +@endpush diff --git a/resources/views/table/propose/layout-row-pagination.blade.php b/resources/views/table/propose/layout-row-pagination.blade.php index 3b078e8..41f4432 100644 --- a/resources/views/table/propose/layout-row-pagination.blade.php +++ b/resources/views/table/propose/layout-row-pagination.blade.php @@ -5,6 +5,10 @@ @extends('table.propose.layout') @section('tab-content') +
+ @yield('header') +
+ @if($rows->hasPages())