Row adding, some webpack tweaks
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user