Issues (18)

src/Dashboard/DashboardView.php (4 issues)

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
The type Epesi\Core\System\View\Form 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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

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 ignore-type  annotation

92
			$columns->js(true)->find('.applet-close')->click(new jsFunction(['e'], [new jsExpression('if (confirm("' .  /** @scrutinizer ignore-type */ __('Delete this applet?') . '")) {$(e.target).closest(".applet").fadeOut(400, function(){var col = $(this).closest(".column.sortable");this.remove();col.trigger("sortupdate");})}')]));
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