x-systems /
epesi-base
| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace Epesi\Base\Dashboard; |
||||
| 4 | |||||
| 5 | use atk4\ui\jsExpression; |
||||
| 6 | use atk4\ui\jsFunction; |
||||
| 7 | use atk4\ui\FormField\Input; |
||||
| 8 | use Epesi\Core\System\View\Form; |
||||
|
0 ignored issues
–
show
|
|||||
| 9 | use Epesi\Core\Layout\View\ActionBar; |
||||
|
0 ignored issues
–
show
The type
Epesi\Core\Layout\View\ActionBar was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths Loading history...
|
|||||
| 10 | use Epesi\Core\System\Modules\ModuleView; |
||||
|
0 ignored issues
–
show
The type
Epesi\Core\System\Modules\ModuleView was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths Loading history...
|
|||||
| 11 | use Epesi\Base\Dashboard\View\Applet; |
||||
| 12 | use Epesi\Base\Dashboard\Models\Dashboard; |
||||
| 13 | use Epesi\Base\Dashboard\Integration\Joints\AppletJoint; |
||||
| 14 | use Illuminate\Support\Facades\Auth; |
||||
| 15 | use atk4\ui\jsReload; |
||||
| 16 | |||||
| 17 | class DashboardView extends ModuleView |
||||
| 18 | { |
||||
| 19 | protected $label = 'Dashboard'; |
||||
| 20 | |||||
| 21 | protected $dashboard; |
||||
| 22 | protected $columns; |
||||
| 23 | protected $admin = false; |
||||
| 24 | protected $locked = false; |
||||
| 25 | |||||
| 26 | public function body() |
||||
| 27 | { |
||||
| 28 | if (! $this->isSingleDashboard()) { |
||||
| 29 | $this->location($this->dashboard()['name']); |
||||
| 30 | } |
||||
| 31 | |||||
| 32 | // initiate the dashboard first |
||||
| 33 | $this->dashboard(); |
||||
| 34 | |||||
| 35 | $this->addMenu(); |
||||
| 36 | |||||
| 37 | $this->showDashboard(); |
||||
| 38 | } |
||||
| 39 | |||||
| 40 | public function showDashboard() |
||||
| 41 | { |
||||
| 42 | $dashboard = $this->dashboard(); |
||||
| 43 | |||||
| 44 | $this->requireJS('sortable.jquery-ui.js'); |
||||
| 45 | |||||
| 46 | $applets = $dashboard->ref('applets')->setOrder(['column', 'row']); |
||||
| 47 | |||||
| 48 | $columns = $this->add(['Columns', 'id' => 'dashboard', 'ui' => 'three stackable grid' . ($this->isLocked()? ' locked': '')]); |
||||
| 49 | |||||
| 50 | foreach ([1, 2, 3] as $columnId) { |
||||
| 51 | $columnApplets = clone $applets; |
||||
| 52 | |||||
| 53 | /** @scrutinizer ignore-call */ |
||||
| 54 | $col = $columns->addColumn([ |
||||
| 55 | '', |
||||
| 56 | 'ui' => 'sortable', |
||||
| 57 | 'attr' => [ |
||||
| 58 | 'dashboard-id' => $dashboard->id, |
||||
| 59 | 'column-id' => $columnId |
||||
| 60 | ] |
||||
| 61 | ]); |
||||
| 62 | |||||
| 63 | foreach ($columnApplets->addCondition('column', $columnId) as $applet) { |
||||
| 64 | $col->add([ |
||||
| 65 | new Applet(), |
||||
| 66 | 'appletId' => $applet['id'], |
||||
| 67 | 'jointClass' => $applet['class'], |
||||
| 68 | 'options' => $applet['options'], |
||||
| 69 | 'locked' => $this->isLocked() |
||||
| 70 | ]); |
||||
| 71 | } |
||||
| 72 | } |
||||
| 73 | |||||
| 74 | if (! $this->isLocked()) { |
||||
| 75 | $columns->js(true)->find('.sortable')->sortable([ |
||||
| 76 | 'cursor' => 'move', |
||||
| 77 | 'handle' => '.panel-sortable-handle', |
||||
| 78 | 'connectWith' => '.sortable', |
||||
| 79 | 'items' => '.applet', |
||||
| 80 | ]); |
||||
| 81 | |||||
| 82 | $columns->js(true)->find('.column.sortable')->on('sortupdate', new jsFunction([ |
||||
| 83 | $columns->add('jsCallback')->set([$this, 'saveColumn'], [ |
||||
| 84 | new jsExpression(' |
||||
| 85 | { |
||||
| 86 | column: $(this).attr("column-id"), |
||||
| 87 | applets: $(this).sortable( "toArray", { attribute: "applet-id" }) |
||||
| 88 | }') |
||||
| 89 | ]) |
||||
| 90 | ])); |
||||
| 91 | |||||
| 92 | $columns->js(true)->find('.applet-close')->click(new jsFunction(['e'], [new jsExpression('if (confirm("' . __('Delete this applet?') . '")) {$(e.target).closest(".applet").fadeOut(400, function(){var col = $(this).closest(".column.sortable");this.remove();col.trigger("sortupdate");})}')])); |
||||
|
0 ignored issues
–
show
Are you sure
__('Delete this applet?') of type array|null|string can be used in concatenation?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 93 | } |
||||
| 94 | |||||
| 95 | $this->columns = $columns; |
||||
| 96 | |||||
| 97 | $this->requireCSS(); |
||||
| 98 | } |
||||
| 99 | |||||
| 100 | public function editDashboard() |
||||
| 101 | { |
||||
| 102 | $dashboard = $this->dashboard(); |
||||
| 103 | |||||
| 104 | $this->location([$dashboard['name'], __('Find Applets')]); |
||||
| 105 | |||||
| 106 | $this->showDashboard(); |
||||
| 107 | |||||
| 108 | ActionBar::addItemButton('back'); |
||||
| 109 | |||||
| 110 | $adminColumn = $this->columns->addColumn(); |
||||
| 111 | |||||
| 112 | $search = $adminColumn->add(new Input([ |
||||
| 113 | 'placeholder' => __('Search applets'), |
||||
| 114 | 'icon' => 'search' |
||||
| 115 | ]))->setStyle(['width' => '100%']); |
||||
| 116 | |||||
| 117 | $search->js(true)->on('keyup', new jsFunction(['e'], [ |
||||
| 118 | new jsExpression(' |
||||
| 119 | var str = $(e.target).val().toLowerCase(); |
||||
| 120 | |||||
| 121 | $("#dashboard_applets_new").children(".applet").each(function(i, nodeObj) { |
||||
| 122 | var node = $(nodeObj); |
||||
| 123 | |||||
| 124 | node.toggle(node.attr("searchkey").indexOf(str) != -1); |
||||
| 125 | }); |
||||
| 126 | ') |
||||
| 127 | ])); |
||||
| 128 | |||||
| 129 | $col = $adminColumn->add([ |
||||
| 130 | 'View', |
||||
| 131 | 'id' => 'dashboard_applets_new', |
||||
| 132 | 'ui' => 'admin sortable', |
||||
| 133 | 'attr' => [ |
||||
| 134 | 'dashboard-id' => $dashboard->id, |
||||
| 135 | 'column-id' => 'admin' |
||||
| 136 | ] |
||||
| 137 | ]); |
||||
| 138 | |||||
| 139 | foreach ( AppletJoint::collect() as $applet ) { |
||||
| 140 | $col->add([ |
||||
| 141 | new Applet(), |
||||
| 142 | 'appletId' => 'new_' . str_ireplace('\\', '-', get_class($applet)), |
||||
| 143 | 'jointClass' => $applet, |
||||
| 144 | 'admin' => 1, |
||||
| 145 | ]); |
||||
| 146 | } |
||||
| 147 | } |
||||
| 148 | |||||
| 149 | public function addMenu() |
||||
| 150 | { |
||||
| 151 | if ($this->isLocked()) return; |
||||
| 152 | |||||
| 153 | $dashboardId = $this->dashboard()->id; |
||||
| 154 | |||||
| 155 | $dashboardMenu = $this->app->layout->menuRight->addMenu([ |
||||
| 156 | '', |
||||
| 157 | 'icon' => 'ellipsis vertical', |
||||
| 158 | 'attr' => [ |
||||
| 159 | 'title' => __('Find dashboard applets') |
||||
| 160 | ] |
||||
| 161 | ]); |
||||
| 162 | |||||
| 163 | $dashboardMenu->js(true)->find('i.dropdown.icon')->remove(); |
||||
| 164 | |||||
| 165 | // ***** edit ***** // |
||||
| 166 | $dashboardMenu->addItem([__('Add applets'), 'icon' => 'edit'])->link($this->selfLink('editDashboard', ['dashboard' => $dashboardId])); |
||||
| 167 | |||||
| 168 | // ***** rename ***** // |
||||
| 169 | $modal = $this->add(['Modal', 'title' => __('Rename ":name" Dashboard', ['name' => $this->dashboard()['name']])])->set(\Closure::fromCallable([$this, 'renameDashboard'])); |
||||
| 170 | |||||
| 171 | $dashboardMenu->addItem([__('Rename dashboard'), 'icon' => 'i cursor'])->on('click', $modal->show()); |
||||
| 172 | |||||
| 173 | // there is only one admin default dashboard |
||||
| 174 | if ($this->admin) return; |
||||
| 175 | |||||
| 176 | // ***** add ***** // |
||||
| 177 | $modal = $this->add(['Modal', 'title' => __('Add Dashboard')])->set(\Closure::fromCallable([$this, 'addDashboard'])); |
||||
| 178 | |||||
| 179 | $dashboardMenu->addItem([__('Add dashboard'), 'icon' => 'add'])->on('click', $modal->show()); |
||||
| 180 | |||||
| 181 | if ($this->isSingleDashboard()) return; |
||||
| 182 | |||||
| 183 | // ***** reorder ***** // |
||||
| 184 | $modal = $this->add(['Modal', 'title' => __('Reorder Dashboards')])->set(\Closure::fromCallable([$this, 'reorderDashboards'])); |
||||
| 185 | |||||
| 186 | $dashboardMenu->addItem([__('Reorder dashboards'), 'icon' => 'sort'])->on('click', $modal->show()); |
||||
| 187 | |||||
| 188 | // ***** delete ***** // |
||||
| 189 | $deleteButton = $dashboardMenu->addItem([__('Delete dashboard'), 'icon' => 'trash', 'attr' => ['title' => __('Delete current dashboard')]]); |
||||
| 190 | |||||
| 191 | $deleteDashboard = $deleteButton->add('jsCallback')->set([$this, 'deleteDashboard'], [$dashboardId]); |
||||
| 192 | |||||
| 193 | $deleteButton->on('click', [ |
||||
| 194 | new jsExpression('if (! confirm([])) return;', [__('Delete current dashboard?')]), |
||||
| 195 | $deleteDashboard |
||||
| 196 | ]); |
||||
| 197 | } |
||||
| 198 | |||||
| 199 | public function addDashboard($view) |
||||
| 200 | { |
||||
| 201 | $form = $view->add(new Form(['buttonSave' => ['Button', __('Create Dashboard'), 'primary']])); |
||||
| 202 | |||||
| 203 | $existing = Dashboard::create()->addCondition('user_id', [0, $this->userId()]); |
||||
| 204 | |||||
| 205 | $existingList = collect($existing->export())->pluck('name', 'id'); |
||||
| 206 | |||||
| 207 | $form->addFields([ |
||||
| 208 | ['name', __('Name')], |
||||
| 209 | ['base', ['DropDown', 'caption' => __('Copy applets from'), 'values' => $existingList]] |
||||
| 210 | ]); |
||||
| 211 | |||||
| 212 | $form->layout->addButton(['Button', __('Cancel')])->on('click', $view->owner->hide()); |
||||
| 213 | |||||
| 214 | $form->onSubmit(function($form) use ($existingList) { |
||||
| 215 | $values = $form->getValues(); |
||||
| 216 | |||||
| 217 | $dashboard = Dashboard::create(); |
||||
| 218 | |||||
| 219 | $dashboardId = (int) $dashboard->insert([ |
||||
| 220 | 'name' => $values['name'], |
||||
| 221 | 'user_id' => $this->userId(), |
||||
| 222 | 'position' => count($existingList) |
||||
| 223 | ]); |
||||
| 224 | |||||
| 225 | if ($values['base']) { |
||||
| 226 | foreach ($dashboard->withID($values['base'])->ref('applets') as $baseApplet) { |
||||
| 227 | // only save on below does not work for some reason. fails when reloading |
||||
| 228 | $baseApplet->duplicate()->saveAndUnload(['dashboard_id' => $dashboardId]); |
||||
| 229 | } |
||||
| 230 | } |
||||
| 231 | |||||
| 232 | return [ |
||||
| 233 | $form->notify(__('Dashboard created, redirecting ...')), |
||||
| 234 | new jsExpression('window.setTimeout(function() {window.location.replace([])}, 1200)', [$this->selfLink('body', ['dashboard' => $dashboardId])]) |
||||
| 235 | ]; |
||||
| 236 | }); |
||||
| 237 | } |
||||
| 238 | |||||
| 239 | public function renameDashboard($view) |
||||
| 240 | { |
||||
| 241 | $form = $view->add(new Form(['buttonSave' => ['Button', __('Save'), 'primary']])); |
||||
| 242 | |||||
| 243 | $form->addField('name', __('New Name'))->set($this->dashboard()['name']); |
||||
| 244 | |||||
| 245 | $form->layout->addButton(['Button', __('Cancel')])->on('click', $view->owner->hide()); |
||||
| 246 | |||||
| 247 | $form->onSubmit(function($form) { |
||||
| 248 | $this->dashboard()->save($form->getValues()); |
||||
| 249 | |||||
| 250 | return [ |
||||
| 251 | $form->notifySuccess(__('Dashboard renamed, reloading ...')), |
||||
| 252 | new jsExpression('window.setTimeout(function() {window.location.replace([])}, 1200)', [$this->selfLink('body', ['dashboard' => $this->dashboard()->id])]) |
||||
| 253 | ]; |
||||
| 254 | }); |
||||
| 255 | } |
||||
| 256 | |||||
| 257 | public function deleteDashboard($jsCallback, $dashboardId) |
||||
| 258 | { |
||||
| 259 | $dashboard = Dashboard::create()->load($dashboardId); |
||||
| 260 | |||||
| 261 | $name = $dashboard['name']; |
||||
| 262 | |||||
| 263 | return $dashboard->delete()? [ |
||||
| 264 | $this->notifySuccess(__('Dashboard ":name" deleted, redirecting ...', compact('name'))), |
||||
| 265 | new jsExpression('window.setTimeout(function() {window.location.replace([])}, 1200)', [$this->selfLink()]) |
||||
| 266 | ]: $this->notifyError(__('Error deleting dashboard')); |
||||
| 267 | } |
||||
| 268 | |||||
| 269 | public function reorderDashboards($view) |
||||
| 270 | { |
||||
| 271 | $grid = $view->add(['Grid', 'paginator' => false, 'menu' => false]); |
||||
| 272 | |||||
| 273 | $grid->setModel($this->userDashboards()->setOrder('position')); |
||||
| 274 | |||||
| 275 | $grid->addDragHandler()->onReorder(function ($order) use ($grid) { |
||||
| 276 | foreach ($this->userDashboards() as $dashboard) { |
||||
| 277 | $dashboard->save(['position' => array_search($dashboard->id, $order)]); |
||||
| 278 | } |
||||
| 279 | |||||
| 280 | return [ |
||||
| 281 | new jsReload($grid), |
||||
| 282 | $this->notifySuccess(__('Dashboards reordered!')) |
||||
| 283 | ]; |
||||
| 284 | }); |
||||
| 285 | |||||
| 286 | $view->add(['View', 'ui' => 'buttons'])->add(['Button', __('Done'), 'primary'])->on('click', new jsExpression('location.reload()')); |
||||
| 287 | } |
||||
| 288 | |||||
| 289 | public function saveColumn($jsCallback, $columnHash) |
||||
| 290 | { |
||||
| 291 | $applets = $columnHash['applets']?? []; |
||||
| 292 | |||||
| 293 | if ($new = preg_grep('/^new_/', $applets)) { |
||||
| 294 | $new = reset($new); |
||||
| 295 | |||||
| 296 | $row = array_search($new, $applets); |
||||
| 297 | |||||
| 298 | $applets[$row] = $this->dashboard()->ref('applets')->insert([ |
||||
| 299 | 'class' => str_ireplace('-', '\\', preg_replace('/^new_/', '', $new)), |
||||
| 300 | 'column' => $columnHash['column'], |
||||
| 301 | 'row' => $row |
||||
| 302 | ]); |
||||
| 303 | } |
||||
| 304 | |||||
| 305 | foreach ($this->dashboard()->ref('applets')->withID($applets) as $applet) { |
||||
| 306 | $applet->save([ |
||||
| 307 | 'column' => $columnHash['column'], |
||||
| 308 | 'row' => array_search($applet->id, $applets) |
||||
| 309 | ]); |
||||
| 310 | } |
||||
| 311 | |||||
| 312 | $removed = $this->dashboard()->ref('applets')->addCondition('column', $columnHash['column']); |
||||
| 313 | |||||
| 314 | if ($applets) { |
||||
| 315 | $removed->addCondition('id', 'not', $applets); |
||||
| 316 | } |
||||
| 317 | |||||
| 318 | $this->dashboard()->ref('applets')->addCondition('column', 0)->action('delete')->execute(); |
||||
| 319 | |||||
| 320 | $removed->action('update')->set('column', 0)->execute(); |
||||
| 321 | } |
||||
| 322 | |||||
| 323 | public function showSettings($appletId) |
||||
| 324 | { |
||||
| 325 | $applet = $this->dashboard()->ref('applets')->load($appletId); |
||||
| 326 | |||||
| 327 | $joint = new $applet['class'](); |
||||
| 328 | |||||
| 329 | $this->location([__('Edit Applet Settings'), $joint->caption()]); |
||||
| 330 | |||||
| 331 | $form = $this->add(new Form()); |
||||
| 332 | $form->addElements($joint->elements()); |
||||
| 333 | $form->confirmLeave(); |
||||
| 334 | |||||
| 335 | $form->model->set($applet['options']); |
||||
| 336 | |||||
| 337 | $form->validate(function(Form $form) use ($applet) { |
||||
| 338 | $applet->save(['options' => $form->model->get()]); |
||||
| 339 | |||||
| 340 | return $form->notifySuccess(__('Settings saved!')); |
||||
| 341 | }); |
||||
| 342 | |||||
| 343 | ActionBar::addItemButton('back'); |
||||
| 344 | |||||
| 345 | ActionBar::addItemButton('save')->on('click', $form->submit()); |
||||
| 346 | } |
||||
| 347 | |||||
| 348 | public function lock() |
||||
| 349 | { |
||||
| 350 | $this->locked = true; |
||||
| 351 | |||||
| 352 | return $this; |
||||
| 353 | } |
||||
| 354 | |||||
| 355 | public function isLocked() |
||||
| 356 | { |
||||
| 357 | return $this->locked || ! Auth::user()->can('edit dashboard'); |
||||
| 358 | } |
||||
| 359 | |||||
| 360 | protected function isSingleDashboard() |
||||
| 361 | { |
||||
| 362 | return $this->userDashboards()->action('count')->getOne() <= 1; |
||||
| 363 | } |
||||
| 364 | |||||
| 365 | /** |
||||
| 366 | * @return Dashboard |
||||
| 367 | * |
||||
| 368 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException |
||||
| 369 | * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException |
||||
| 370 | */ |
||||
| 371 | protected function dashboard() |
||||
| 372 | { |
||||
| 373 | if (! is_object($this->dashboard)) { |
||||
| 374 | $this->dashboard = $this->dashboard? Dashboard::create()->tryLoad($this->dashboard): $this->defaultUserDashboard(); |
||||
| 375 | } |
||||
| 376 | |||||
| 377 | return $this->dashboard?: abort(404); |
||||
| 378 | } |
||||
| 379 | |||||
| 380 | protected function defaultUserDashboard() |
||||
| 381 | { |
||||
| 382 | $userDashboard = $this->userDashboards()->setOrder('position')->tryLoadAny(); |
||||
| 383 | |||||
| 384 | if (! $userDashboard->loaded()) { |
||||
| 385 | $this->lock(); |
||||
| 386 | |||||
| 387 | $userDashboard = $this->defaultSystemDashboard(); |
||||
| 388 | } |
||||
| 389 | |||||
| 390 | return $userDashboard; |
||||
| 391 | } |
||||
| 392 | |||||
| 393 | protected function defaultSystemDashboard() |
||||
| 394 | { |
||||
| 395 | return $this->userDashboards(0)->setOrder('position')->tryLoadAny(); |
||||
| 396 | } |
||||
| 397 | |||||
| 398 | protected function userDashboards($userId = null) |
||||
| 399 | { |
||||
| 400 | return Dashboard::create()->addCondition('user_id', $userId ?? $this->userId()); |
||||
| 401 | } |
||||
| 402 | |||||
| 403 | /** |
||||
| 404 | * @return number |
||||
| 405 | */ |
||||
| 406 | protected function userId() |
||||
| 407 | { |
||||
| 408 | return $this->admin? 0: Auth::id(); |
||||
| 409 | } |
||||
| 410 | } |
||||
| 411 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths