Row adding, some webpack tweaks

This commit is contained in:
2018-08-05 23:26:37 +02:00
parent 671fb9b024
commit ace61f2a07
26 changed files with 628 additions and 133 deletions
+1 -1
View File
@@ -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);
}
+2 -28
View File
@@ -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()]);
}
+30 -2
View File
@@ -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;
+76
View File
@@ -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
View File
@@ -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());
}
}
+1 -1
View File
@@ -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
+6 -19
View File
@@ -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);
}
}
+3 -16
View File
@@ -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
View File
@@ -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();
}