diff --git a/Makefile b/Makefile
index 72f3b85..e339e40 100644
--- a/Makefile
+++ b/Makefile
@@ -10,3 +10,7 @@ ana:
npm run dev-analyze
prod:
npm run prod
+bc:
+ php artisan view:clear
+ php artisan cache:clear
+ php artisan ide-helper:generate
diff --git a/_json_typehints.php b/_json_typehints.php
index 6565ac6..6c1ad90 100644
--- a/_json_typehints.php
+++ b/_json_typehints.php
@@ -13,6 +13,7 @@ interface RowData {}
* @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 mixed[] $_loadvals - values after transformation, key by CID / for use by JS to detect changes since page load
* @property string[] $_changed - values that were changed
*/
interface DecoratedRow extends RowData {}
diff --git a/app/Http/Controllers/TableEditController.php b/app/Http/Controllers/TableEditController.php
index a37ca85..7829546 100644
--- a/app/Http/Controllers/TableEditController.php
+++ b/app/Http/Controllers/TableEditController.php
@@ -60,8 +60,9 @@ class TableEditController extends Controller
if ($tableModel === null) abort(404, "No such table.");
if ($tab == null) $tab = 'edit-rows';
- $tabs = ['edit-rows', 'add-rows', 'manage-columns', 'review'];
- if (!in_array($tab, $tabs)) abort(404, "No such tab: $tab");
+
+ $method = camel_case($tab);
+ if (!method_exists($this, $method)) abort(404, "No such tab: $tab");
$changeset = $this->getChangeset($tableModel);
@@ -69,7 +70,7 @@ class TableEditController extends Controller
dd($changeset);
}
- return $this->{camel_case($tab)}($changeset);
+ return $this->$method($changeset);
}
/** @noinspection PhpUnusedPrivateMethodInspection */
@@ -101,6 +102,18 @@ class TableEditController extends Controller
]);
}
+ /** @noinspection PhpUnusedPrivateMethodInspection */
+ private function addRowsCsv(Changeset $changeset)
+ {
+ $columns = $changeset->fetchAndTransformColumns();
+
+ return view('table.propose.add-rows-csv', [
+ 'changeset' => $changeset,
+ 'table' => $changeset->table,
+ 'columns' => collect($columns),
+ ]);
+ }
+
/** @noinspection PhpUnusedPrivateMethodInspection */
private function manageColumns(Changeset $changeset)
{
@@ -143,13 +156,43 @@ class TableEditController extends Controller
$resp = $updated;
break;
+ case 'row.add-csv':
+ $fname = 'csv-file';
+ if ($request->hasFile($fname)) {
+ try {
+ $file = $request->file($fname);
+ if ($file->isValid() && $file->isReadable()) {
+ $handle = $file->openFile();
+ $csv = Utils::csvToArray($handle);
+ if (empty($csv)) throw new \Exception("Failed to parse CSV file.");
+
+ $changeset->addFilledRows($csv);
+ $handle = null;
+ } else {
+ throw new \Exception("File not valid.");
+ }
+ } catch (\Exception $e) {
+ return $this->backWithErrors(['csv-file' => $e->getMessage()]);
+ }
+ }
+ else {
+ try {
+ $text = trim($input->data);
+ if (empty($text)) throw new \Exception("Empty CSV field and no file uploaded.");
+ $changeset->addFilledRows(Utils::csvToArray($text));
+ } catch (\Exception $e) {
+ return $this->backWithErrors(['data' => $e->getMessage()]);
+ }
+ }
+ break;
+
case 'row.remove':
$isNew = $changeset->isNewRow($input->id);
$changeset->rowRemove($input->id);
$resp = $isNew ? null : $changeset->fetchAndTransformRow($input->id);
break;
- case 'rows.remove-empty-new':
+ case 'row.remove-empty-new':
$changeset->removeEmptyNewRows();
break;
@@ -162,14 +205,6 @@ class TableEditController extends Controller
$changeset->addBlankRows($input->count);
break;
- case 'rows.add-csv':
- try {
- $changeset->addFilledRows(Utils::csvToArray($input->data));
- } catch (\Exception $e) {
- return $this->backWithErrors(['data' => $e->getMessage()]);
- }
- break;
-
case 'col.update':
$data = (object)$input->data;
$resp = $changeset->columnUpdate($data);
diff --git a/app/Tables/Changeset.php b/app/Tables/Changeset.php
index 391d057..3c7c948 100644
--- a/app/Tables/Changeset.php
+++ b/app/Tables/Changeset.php
@@ -188,16 +188,19 @@ class Changeset
$row->_orig = [];
}
+ if ($decorate) {
+ $row->_orig = array_diff((array)$row, []);
+ // remove junk
+ unset($row->_orig['_id']);
+ unset($row->_orig['_new']);
+ unset($row->_orig['_remove']);
+ unset($row->_orig['_changed']);
+ unset($row->_orig['_orig']);
+ }
+
if ($this->isNewRow($row->_id)) {
if ($decorate) {
$row->_new = true;
- $row->_orig = array_diff((array)$row, []);
- // remove junk
- unset($row->_orig['_id']);
- unset($row->_orig['_new']);
- unset($row->_orig['_remove']);
- unset($row->_orig['_changed']);
- unset($row->_orig['_orig']);
}
return $row;
}
@@ -235,6 +238,16 @@ class Changeset
unset($row->_row_pivot);
+ if ($decorate) {
+ $row->_loadvals = array_diff((array)$row, []);
+ // remove junk
+ unset($row->_loadvals['_id']);
+ unset($row->_loadvals['_new']);
+ unset($row->_loadvals['_remove']);
+ unset($row->_loadvals['_changed']);
+ unset($row->_loadvals['_orig']);
+ }
+
return $row;
}
@@ -582,6 +595,7 @@ class Changeset
'type' => "string",
'title' => "Column {$num}",
'id' => $cid,
+ '_new' => true,
];
$this->newColumns[$cid] = $col;
diff --git a/app/View/WidgetFactory.php b/app/View/WidgetFactory.php
index 1be780a..c74cfc0 100644
--- a/app/View/WidgetFactory.php
+++ b/app/View/WidgetFactory.php
@@ -38,6 +38,19 @@ class WidgetFactory
"";
}
+ public function labeledPar($label, $text, $extraClasses='', $escape=true)
+ {
+ if (false === strpos($extraClasses, 'mb-')) $extraClasses .= ' mb-2';
+
+ return
+ "
".
+ "
".
+ "
fieldCols".e($extraClasses)."\">".
+ ($escape ? e($text) : $text) .
+ "
".
+ "
";
+ }
+
/**
* Convert the given string to a-href if it is a link.
*
@@ -85,6 +98,11 @@ class WidgetFactory
return $this->baseWidget('input', $name, $label)->type('email');
}
+ public function file($name, $label)
+ {
+ return $this->baseWidget('input', $name, $label)->type('file');
+ }
+
public function checkbox($name, $label)
{
return (new CheckboxWidget('checkbox', $name, $label))
diff --git a/porklib/Utils/Utils.php b/porklib/Utils/Utils.php
index 7b2789f..45497e5 100644
--- a/porklib/Utils/Utils.php
+++ b/porklib/Utils/Utils.php
@@ -1014,8 +1014,25 @@ class Utils
return $key;
}
- public static function csvToArray(string $data)
+ /**
+ * @param \SplFileObject|string $data
+ * @return array
+ */
+ public static function csvToArray($data)
{
- return array_map('str_getcsv', explode("\n", $data));
+ if ($data instanceof \SplFileObject) {
+ $data->setFlags(\SplFileObject::SKIP_EMPTY | \SplFileObject::DROP_NEW_LINE);
+ $lines = [];
+ while (! $data->eof()) {
+ $line = $data->fgetcsv();
+ if ($line !== null) {
+ $lines[] = $line;
+ }
+ }
+ return $lines;
+ }
+ else {
+ return array_map('str_getcsv', explode("\n", $data));
+ }
}
}
diff --git a/public/fonts/fa-dtbl-1-preview.html b/public/fonts/fa-dtbl-1-preview.html
index dc7a923..5824a59 100644
--- a/public/fonts/fa-dtbl-1-preview.html
+++ b/public/fonts/fa-dtbl-1-preview.html
@@ -184,6 +184,7 @@
.fa-key-modern:before,
.fa-link:before,
.fa-moon-o:before,
+.fa-paper-plane-o:before,
.fa-pencil:before,
.fa-plus:before,
.fa-question-circle:before,
@@ -198,6 +199,7 @@
.fa-th-list:before,
.fa-trash-o:before,
.fa-undo:before,
+.fa-upload:before,
.fa-user:before,
.fa-user-circle-o:before,
.fa-user-plus:before,
@@ -240,25 +242,27 @@
.fa-key-modern:before { content: "\f114"; }
.fa-link:before { content: "\f115"; }
.fa-moon-o:before { content: "\f116"; }
-.fa-pencil:before { content: "\f117"; }
-.fa-plus:before { content: "\f118"; }
-.fa-question-circle:before { content: "\f119"; }
-.fa-reply:before { content: "\f11a"; }
-.fa-sign-in:before { content: "\f11b"; }
-.fa-sign-out:before { content: "\f11c"; }
-.fa-spinner:before { content: "\f11d"; }
-.fa-star:before { content: "\f11e"; }
-.fa-star-o:before { content: "\f11f"; }
-.fa-sun-o:before { content: "\f120"; }
-.fa-table:before { content: "\f121"; }
-.fa-th-list:before { content: "\f122"; }
-.fa-trash-o:before { content: "\f123"; }
-.fa-undo:before { content: "\f124"; }
-.fa-user:before { content: "\f125"; }
-.fa-user-circle-o:before { content: "\f126"; }
-.fa-user-plus:before { content: "\f127"; }
-.fa-users:before { content: "\f128"; }
-.fa-wrench:before { content: "\f129"; }
+.fa-paper-plane-o:before { content: "\f117"; }
+.fa-pencil:before { content: "\f118"; }
+.fa-plus:before { content: "\f119"; }
+.fa-question-circle:before { content: "\f11a"; }
+.fa-reply:before { content: "\f11b"; }
+.fa-sign-in:before { content: "\f11c"; }
+.fa-sign-out:before { content: "\f11d"; }
+.fa-spinner:before { content: "\f11e"; }
+.fa-star:before { content: "\f11f"; }
+.fa-star-o:before { content: "\f120"; }
+.fa-sun-o:before { content: "\f121"; }
+.fa-table:before { content: "\f122"; }
+.fa-th-list:before { content: "\f123"; }
+.fa-trash-o:before { content: "\f124"; }
+.fa-undo:before { content: "\f125"; }
+.fa-upload:before { content: "\f126"; }
+.fa-user:before { content: "\f127"; }
+.fa-user-circle-o:before { content: "\f128"; }
+.fa-user-plus:before { content: "\f129"; }
+.fa-users:before { content: "\f12a"; }
+.fa-wrench:before { content: "\f12b"; }
@@ -274,7 +278,7 @@
+
+
+ PpPpPpPpPpPpPpPpPpPp
+
+
+ 12141618212436486072
+
+
+
+
+
+
+
@@ -603,7 +620,7 @@
-
+
@@ -616,7 +633,7 @@
-
+
@@ -629,7 +646,7 @@
-
+
@@ -642,7 +659,7 @@
-
+
@@ -655,7 +672,7 @@
-
+
@@ -668,7 +685,7 @@
-
+
@@ -681,7 +698,7 @@
-
+
@@ -694,7 +711,7 @@
-
+
@@ -707,7 +724,7 @@
-
+
@@ -720,7 +737,7 @@
-
+
@@ -733,7 +750,7 @@
-
+
@@ -746,7 +763,7 @@
-
+
@@ -759,7 +776,20 @@
-
+
+
+
+
+
+
+ PpPpPpPpPpPpPpPpPpPp
+
+
+ 12141618212436486072
+
+
+
+
@@ -772,7 +802,7 @@
-
+
@@ -785,7 +815,7 @@
-
+
@@ -798,7 +828,7 @@
-
+
@@ -811,7 +841,7 @@
-
+
@@ -824,7 +854,7 @@
-
+
diff --git a/public/fonts/fa-dtbl-1.css b/public/fonts/fa-dtbl-1.css
index 85efae6..b8e2d47 100644
--- a/public/fonts/fa-dtbl-1.css
+++ b/public/fonts/fa-dtbl-1.css
@@ -61,22 +61,24 @@
.fa-key-modern::before { content: "\f114"; }
.fa-link::before { content: "\f115"; }
.fa-moon-o::before { content: "\f116"; }
-.fa-pencil::before { content: "\f117"; }
-.fa-plus::before { content: "\f118"; }
-.fa-question-circle::before { content: "\f119"; }
-.fa-reply::before { content: "\f11a"; }
-.fa-sign-in::before { content: "\f11b"; }
-.fa-sign-out::before { content: "\f11c"; }
-.fa-spinner::before { content: "\f11d"; }
-.fa-star::before { content: "\f11e"; }
-.fa-star-o::before { content: "\f11f"; }
-.fa-sun-o::before { content: "\f120"; }
-.fa-table::before { content: "\f121"; }
-.fa-th-list::before { content: "\f122"; }
-.fa-trash-o::before { content: "\f123"; }
-.fa-undo::before { content: "\f124"; }
-.fa-user::before { content: "\f125"; }
-.fa-user-circle-o::before { content: "\f126"; }
-.fa-user-plus::before { content: "\f127"; }
-.fa-users::before { content: "\f128"; }
-.fa-wrench::before { content: "\f129"; }
+.fa-paper-plane-o::before { content: "\f117"; }
+.fa-pencil::before { content: "\f118"; }
+.fa-plus::before { content: "\f119"; }
+.fa-question-circle::before { content: "\f11a"; }
+.fa-reply::before { content: "\f11b"; }
+.fa-sign-in::before { content: "\f11c"; }
+.fa-sign-out::before { content: "\f11d"; }
+.fa-spinner::before { content: "\f11e"; }
+.fa-star::before { content: "\f11f"; }
+.fa-star-o::before { content: "\f120"; }
+.fa-sun-o::before { content: "\f121"; }
+.fa-table::before { content: "\f122"; }
+.fa-th-list::before { content: "\f123"; }
+.fa-trash-o::before { content: "\f124"; }
+.fa-undo::before { content: "\f125"; }
+.fa-upload::before { content: "\f126"; }
+.fa-user::before { content: "\f127"; }
+.fa-user-circle-o::before { content: "\f128"; }
+.fa-user-plus::before { content: "\f129"; }
+.fa-users::before { content: "\f12a"; }
+.fa-wrench::before { content: "\f12b"; }
diff --git a/public/fonts/fa-dtbl-1.eot b/public/fonts/fa-dtbl-1.eot
index f7fcd1f..d2f207c 100644
Binary files a/public/fonts/fa-dtbl-1.eot and b/public/fonts/fa-dtbl-1.eot differ
diff --git a/public/fonts/fa-dtbl-1.svg b/public/fonts/fa-dtbl-1.svg
index 44eae9d..028a445 100644
--- a/public/fonts/fa-dtbl-1.svg
+++ b/public/fonts/fa-dtbl-1.svg
@@ -5,7 +5,7 @@
-->