Row adding, some webpack tweaks
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Tables;
|
||||
|
||||
|
||||
abstract class BaseNumerator
|
||||
{
|
||||
/** @var int */
|
||||
protected $next;
|
||||
|
||||
/** @var int */
|
||||
protected $last;
|
||||
|
||||
/**
|
||||
* BaseNumerator constructor.
|
||||
*
|
||||
* @param int|int[] $first - first index, or [first, last]
|
||||
* @param int|null $last - last index, or null
|
||||
*/
|
||||
public function __construct($first, $last = null)
|
||||
{
|
||||
if (is_array($first) && $last === null) {
|
||||
list($first, $last) = $first;
|
||||
}
|
||||
|
||||
$this->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+125
-13
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Debugbar Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Debugbar is enabled by default, when debug is set to true in app.php.
|
||||
| You can override the value by setting enable to true or false instead of null.
|
||||
|
|
||||
| You can provide an array of URI's that must be ignored (eg. 'api/*')
|
||||
|
|
||||
*/
|
||||
|
||||
'enabled' => 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 </body>, 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,
|
||||
];
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+17
-5
@@ -4,7 +4,7 @@
|
||||
<tr>
|
||||
<th style="width:3rem" class="border-top-0"></th>
|
||||
<th style="width:3rem" class="border-top-0"></th>
|
||||
<th v-for="col in columns" :class="colClasses(col)">{{col.title}}</th>
|
||||
<th v-for="col in columns" :class="colClasses(col)" :title="col.name">{{col.title}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -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) {
|
||||
Vendored
+10
-3
@@ -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 }
|
||||
|
||||
Vendored
+24
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="{{ asset('js/app.js') }}" defer></script>
|
||||
<script>
|
||||
function ready(fn) {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
</script>
|
||||
@stack('scripts')
|
||||
|
||||
<!-- Styles -->
|
||||
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
|
||||
|
||||
@@ -11,14 +11,16 @@
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
@foreach($columns as $col)
|
||||
<th>{{ $col->title }}</th>
|
||||
<th title="{{$col->name}}">{{ $col->title }}</th>
|
||||
@endforeach
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($rows as $row)
|
||||
<tr>
|
||||
<td>{{$row->_id}}</td>
|
||||
@foreach($columns as $col)
|
||||
<td>{{ $row->{$col->name} }}</td>
|
||||
@endforeach
|
||||
|
||||
@@ -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('
|
||||
<div class="text-left">
|
||||
Column parameters in CSV format:
|
||||
<ul class="pl-3 mb-0">
|
||||
<li><b>column identifier</b><br>letters, numbers, underscore
|
||||
<li><b>column data type</b><br>int, string, float, bool
|
||||
<li><b>column title</b><br>used for display (optional)
|
||||
</ul>
|
||||
</div>') !!--}}
|
||||
|
||||
<div class="row form-group">
|
||||
<label for="field-columns" class="col-md-3 col-form-label text-md-right">
|
||||
Columns
|
||||
</label>
|
||||
<div class="col-md-8">
|
||||
<column-editor name="columns" :initial-columns="{{old('columns', toJSON($columns))}}"></column-editor>
|
||||
<div id="column-editor"></div>
|
||||
|
||||
<noscript>
|
||||
You have JavaScript disabled; enter columns as JSON array<br>
|
||||
@@ -84,3 +73,14 @@
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
ready(function() {
|
||||
app.ColumnEditor('#column-editor', {
|
||||
name: 'columns',
|
||||
initialColumns: {!! old('columns', toJSON($columns)) !!},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -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')
|
||||
<div class="form-inline py-2 px-1 border-bottom mb-3">
|
||||
{{-- TODO improve layout --}}
|
||||
<form action="{{$table->draftUpdateRoute}}" method="POST" class="form-inline">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="rows.add-csv">
|
||||
<input type="hidden" name="redirect" value="{{request()->fullUrl()}}">
|
||||
<label class="pr-2" for="csv-data">Add CSV:</label>
|
||||
<textarea name="data" id="csv-data"
|
||||
title="{{$errors->has('data') ? $errors->first('data') : ''}}"
|
||||
class="form-control mr-1 {{ $errors->has('data')?'is-invalid':'' }}"
|
||||
style="width:30em; height:10em">{{old('data')}}</textarea>
|
||||
<button class="btn btn-outline-success">Append</button>
|
||||
</form>
|
||||
|
||||
<div class="flex-grow-1"></div>
|
||||
|
||||
<form action="{{$table->draftUpdateRoute}}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="rows.add">
|
||||
<input type="hidden" name="redirect" value="{{request()->fullUrl()}}">
|
||||
<label class="form-inline pr-2" for="newrow-count">Add Empty Rows:</label>
|
||||
<input name="count" id="newrow-count" type="number" min=1 step=1 value=1 class="form-control mr-1" style="width:10em">
|
||||
<button class="btn btn-outline-success">Add</button>
|
||||
</form>
|
||||
</div>
|
||||
@stop
|
||||
|
||||
@section('rows')
|
||||
<div id="rows-editor"></div>
|
||||
@stop
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
ready(function() {
|
||||
app.RowsEditor('#rows-editor', {
|
||||
route: {!! toJSON($table->draftUpdateRoute) !!},
|
||||
columns: {!! toJSON($columns) !!},
|
||||
xRows: {!! toJSON($rows->keyBy('_id'), true) !!},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -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
|
||||
|
||||
<table is="row-editor"
|
||||
route="{{$table->draftUpdateRoute}}"
|
||||
:columns="{{toJSON($columns)}}"
|
||||
:x-rows="{{toJSON($transformed)}}">
|
||||
</table>
|
||||
<div id="rows-editor"></div>
|
||||
@stop
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
ready(function() {
|
||||
app.RowsEditor('#rows-editor', {
|
||||
route: {!! toJSON($table->draftUpdateRoute) !!},
|
||||
columns: {!! toJSON($columns) !!},
|
||||
xRows: {!! toJSON($transformed, true) !!},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
@extends('table.propose.layout')
|
||||
|
||||
@section('tab-content')
|
||||
<div class="col-12">
|
||||
@yield('header')
|
||||
</div>
|
||||
|
||||
@if($rows->hasPages())
|
||||
<div class="col-md-12 d-flex">
|
||||
<nav class="text-center" aria-label="Pages of the table">
|
||||
|
||||
@@ -14,9 +14,7 @@ if (!isset($tab) || $tab == '') $tab = 'edit-rows';
|
||||
<small class="flex-grow-1" style="font-size: 120%;">
|
||||
<a href="{{ route('profile.view', $table->owner->name) }}" class="link-no-color">{{ $table->owner->handle }}</a>{{--
|
||||
--}}<span class="px-1">/</span>{{--
|
||||
--}}<b>
|
||||
<a href="{{ $table->viewRoute }}" class="link-no-color">{{ $table->name }}</a>
|
||||
</b>
|
||||
--}}<b><a href="{{ $table->viewRoute }}" class="link-no-color">{{ $table->name }}</a></b>
|
||||
</small>
|
||||
|
||||
<h1 class="mx-3">
|
||||
|
||||
Vendored
+13
-7
@@ -13,21 +13,27 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
|
||||
|
|
||||
*/
|
||||
|
||||
let plugins = [
|
||||
new webpack.ProvidePlugin({
|
||||
'$': 'jquery',
|
||||
jQuery: 'jquery',
|
||||
}),
|
||||
]
|
||||
|
||||
if (process.env.WP_ANALYZE) {
|
||||
plugins.push(new BundleAnalyzerPlugin())
|
||||
}
|
||||
|
||||
mix.webpackConfig({
|
||||
// Force jquery slim - https://code.luasoftware.com/tutorials/webpack/force-jquery-slim-import-in-webpack/
|
||||
resolve: {
|
||||
extensions: ['.js'],
|
||||
alias: {
|
||||
'jquery': 'jquery/dist/jquery.slim.js',
|
||||
'vue$': 'vue/dist/vue.runtime.js',
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
'$': 'jquery',
|
||||
jQuery: 'jquery',
|
||||
}),
|
||||
new BundleAnalyzerPlugin(),
|
||||
],
|
||||
plugins,
|
||||
});
|
||||
|
||||
mix.js('resources/assets/js/app.js', 'public/js')
|
||||
|
||||
Reference in New Issue
Block a user