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 @@
-

fa-dtbl-1 contains 42 glyphs:

+

fa-dtbl-1 contains 44 glyphs:

Toggle Preview Characters
@@ -581,6 +585,19 @@
+
+
+ PpPpPpPpPpPpPpPpPpPp +
+
+ 12141618212436486072 +
+
+ + +
+
+
PpPpPpPpPpPpPpPpPpPp @@ -590,7 +607,7 @@
- +
@@ -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 @@ --> -Created by FontForge 20170805 at Fri Aug 10 18:09:07 2018 +Created by FontForge 20170805 at Fri Aug 10 18:42:51 2018 By ondra The Fork Awesome font is licensed under the SIL OFL 1.1 (http://scripts.sil.org/OFL). Fork Awesome is a fork based of off Font Awesome 4.7.0 by Dave Gandy. More info on licenses at https://forkawesome.github.io @@ -19,10 +19,10 @@ The Fork Awesome font is licensed under the SIL OFL 1.1 (http://scripts.sil.org/ panose-1="2 0 5 3 0 0 0 0 0 0" ascent="1536" descent="-256" - bbox="-0.14014 -256.168 2048 1536.01" + bbox="-0.202124 -256.168 2048 1536.16" underline-thickness="89.6" underline-position="-179.2" - unicode-range="U+0020-F129" + unicode-range="U+0020-F12B" /> - + - - - - - - - - - - - - - - + - - - - diff --git a/public/fonts/fa-dtbl-1.ttf b/public/fonts/fa-dtbl-1.ttf index 36204ac..e58cb69 100644 Binary files a/public/fonts/fa-dtbl-1.ttf and b/public/fonts/fa-dtbl-1.ttf differ diff --git a/public/fonts/fa-dtbl-1.woff2 b/public/fonts/fa-dtbl-1.woff2 index 4956c12..e70226a 100644 Binary files a/public/fonts/fa-dtbl-1.woff2 and b/public/fonts/fa-dtbl-1.woff2 differ diff --git a/resources/assets/fa-config/wanted.ini b/resources/assets/fa-config/wanted.ini index e4820ed..b460844 100644 --- a/resources/assets/fa-config/wanted.ini +++ b/resources/assets/fa-config/wanted.ini @@ -44,6 +44,7 @@ undo # Used in table editor spinner paper-plane-o # save proposal +upload # Upload a file # dark mode sun-o diff --git a/resources/assets/js/components/ColumnEditor.vue b/resources/assets/js/components/ColumnEditor.vue index 64143da..74a1214 100644 --- a/resources/assets/js/components/ColumnEditor.vue +++ b/resources/assets/js/components/ColumnEditor.vue @@ -5,7 +5,12 @@ Complex animated column editor for the table edit page
- +
@@ -16,7 +21,12 @@ Complex animated column editor for the table edit page - +
+ +
@@ -107,6 +130,10 @@ Rows are identified by row._id, columns by col.id th { border-top: 0 none; } + + table.new-rows td { + border: 0 none; + }