mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-01 12:19:28 +00:00
Merge branch 'feature-24647/Konfigurierbare_Dashboards' into feature-25999/C4
This commit is contained in:
@@ -59,5 +59,4 @@ class Test extends Auth_Controller
|
||||
|
||||
if (!$this->_uid) show_error('User authentification failed');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class Api extends Auth_Controller
|
||||
array(
|
||||
'index' => 'dashboard/admin:rw',
|
||||
'getNews' => 'dashboard/benutzer:r',
|
||||
'getAmpeln' => 'dashboard/benutzer:r',
|
||||
'getAmpeln' => 'dashboard/benutzer:r',
|
||||
)
|
||||
);
|
||||
|
||||
@@ -34,9 +34,8 @@ class Api extends Auth_Controller
|
||||
|
||||
$result = $this->NewsModel->getAll($limit);
|
||||
|
||||
if(hasData($result))
|
||||
if (hasData($result))
|
||||
{
|
||||
|
||||
$this->outputJson(getData($result), REST_Controller::HTTP_OK);
|
||||
}
|
||||
else
|
||||
@@ -49,21 +48,20 @@ class Api extends Auth_Controller
|
||||
/**
|
||||
* Get Ampeln.
|
||||
*/
|
||||
public function getAmpeln(){
|
||||
public function getAmpeln()
|
||||
{
|
||||
|
||||
$this->load->model('content/Ampel_model', 'AmpelModel');
|
||||
$result = $this->AmpelModel->getByUser($this->_uid);
|
||||
|
||||
if(hasData($result))
|
||||
if (hasData($result))
|
||||
{
|
||||
|
||||
$this->outputJson(getData($result), REST_Controller::HTTP_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->terminateWithJsonError('fehler entdeckt');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,9 +18,9 @@ class Config extends Auth_Controller
|
||||
'removeWidgetFromPreset' => 'dashboard/admin:rw',
|
||||
'addWidgetsToUserOverride' => 'dashboard/benutzer:rw',
|
||||
'removeWidgetFromUserOverride' => 'dashboard/benutzer:rw',
|
||||
'Funktionen' => 'dashboard/admin:r',
|
||||
'Preset' => 'dashboard/admin:r',
|
||||
'PresetBatch' => 'dashboard/admin:r'
|
||||
'funktionen' => 'dashboard/admin:r',
|
||||
'preset' => 'dashboard/admin:r',
|
||||
'presetBatch' => 'dashboard/admin:r'
|
||||
)
|
||||
);
|
||||
|
||||
@@ -29,7 +29,7 @@ class Config extends Auth_Controller
|
||||
$this->load->model('ressource/Funktion_model', 'FunktionModel');
|
||||
}
|
||||
|
||||
public function index()
|
||||
public function index()
|
||||
{
|
||||
$dashboard_kurzbz = $this->input->get('db');
|
||||
$uid = $this->AuthLib->getAuthObj()->username;
|
||||
@@ -46,7 +46,7 @@ class Config extends Auth_Controller
|
||||
$this->outputJsonSuccess($mergedconfig);
|
||||
}
|
||||
|
||||
public function genWidgetId()
|
||||
public function genWidgetId()
|
||||
{
|
||||
$dashboard_kurzbz = $this->input->get('db');
|
||||
$widgetid = $this->DashboardLib->generateWidgetId($dashboard_kurzbz);
|
||||
@@ -55,23 +55,22 @@ class Config extends Auth_Controller
|
||||
));
|
||||
}
|
||||
|
||||
public function addWidgetsToPreset()
|
||||
public function addWidgetsToPreset()
|
||||
{
|
||||
$input = json_decode($this->input->raw_input_stream);
|
||||
$dashboard_kurzbz = $input->db;
|
||||
$funktion_kurzbz = $input->funktion_kurzbz;
|
||||
|
||||
$preset = $this->DashboardLib->getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz);
|
||||
$preset = $this->DashboardLib->getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz);
|
||||
|
||||
$preset_decoded = json_decode($preset->preset, true);
|
||||
|
||||
$this->DashboardLib->addWidgetsToWidgets($preset_decoded['widgets'],
|
||||
$dashboard_kurzbz, $funktion_kurzbz, $input->widgets);
|
||||
$this->DashboardLib->addWidgetsToWidgets($preset_decoded['widgets'], $dashboard_kurzbz, $funktion_kurzbz, $input->widgets);
|
||||
|
||||
$preset->preset = json_encode($preset_decoded);
|
||||
|
||||
$result = $this->DashboardLib->insertOrUpdatePreset($preset);
|
||||
if( isError($result) ) {
|
||||
if (isError($result)) {
|
||||
http_response_code(500);
|
||||
$this->terminateWithJsonError('preset could not be saved');
|
||||
}
|
||||
@@ -87,16 +86,13 @@ class Config extends Auth_Controller
|
||||
$widgetid = $input->widgetid;
|
||||
|
||||
$preset = $this->DashboardLib->getPreset($dashboard_kurzbz, $funktion_kurzbz);
|
||||
if( $preset === null ) {
|
||||
if ($preset === null) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError('preset for dashboard '
|
||||
. $dashboard_kurzbz . ' and funktion ' . $funktion_kurzbz
|
||||
. ' not found.');
|
||||
$this->terminateWithJsonError('preset for dashboard ' . $dashboard_kurzbz . ' and funktion ' . $funktion_kurzbz . ' not found.');
|
||||
}
|
||||
|
||||
$preset_decoded = json_decode($preset->preset, true);
|
||||
if (!$this->DashboardLib->removeWidgetFromWidgets($preset_decoded['widgets'],
|
||||
$funktion_kurzbz, $widgetid))
|
||||
if (!$this->DashboardLib->removeWidgetFromWidgets($preset_decoded['widgets'], $funktion_kurzbz, $widgetid))
|
||||
{
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError('widgetid ' . $widgetid . ' not found');
|
||||
@@ -104,32 +100,31 @@ class Config extends Auth_Controller
|
||||
|
||||
$preset->preset = json_encode($preset_decoded);
|
||||
$result = $this->DashboardLib->insertOrUpdatePreset($preset);
|
||||
if( isError($result) )
|
||||
if (isError($result))
|
||||
{
|
||||
http_response_code(500);
|
||||
$this->terminateWithJsonError('failed to remove widget');
|
||||
$this->terminateWithJsonError('failed to remove widget');
|
||||
}
|
||||
$this->outputJsonSuccess(array('msg' => 'preset successfully updated.'));
|
||||
}
|
||||
|
||||
public function addWidgetsToUserOverride()
|
||||
public function addWidgetsToUserOverride()
|
||||
{
|
||||
$input = json_decode($this->input->raw_input_stream);
|
||||
$dashboard_kurzbz = $input->db;
|
||||
$funktion_kurzbz = $input->funktion_kurzbz;
|
||||
$uid = $this->AuthLib->getAuthObj()->username;
|
||||
|
||||
$override = $this->DashboardLib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid);
|
||||
$override = $this->DashboardLib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid);
|
||||
|
||||
$override_decoded = json_decode($override->override, true);
|
||||
|
||||
$this->DashboardLib->addWidgetsToWidgets($override_decoded['widgets'],
|
||||
$dashboard_kurzbz, $funktion_kurzbz, $input->widgets);
|
||||
$this->DashboardLib->addWidgetsToWidgets($override_decoded['widgets'], $dashboard_kurzbz, $funktion_kurzbz, $input->widgets);
|
||||
|
||||
$override->override = json_encode($override_decoded);
|
||||
|
||||
$result = $this->DashboardLib->insertOrUpdateOverride($override);
|
||||
if( isError($result) ) {
|
||||
if (isError($result)) {
|
||||
http_response_code(500);
|
||||
$this->terminateWithJsonError('override could not be saved');
|
||||
}
|
||||
@@ -146,16 +141,14 @@ class Config extends Auth_Controller
|
||||
$widgetid = $input->widgetid;
|
||||
|
||||
$override = $this->DashboardLib->getOverride($dashboard_kurzbz, $uid);
|
||||
if( empty($override) ) {
|
||||
if (empty($override)) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError('userconfig for dashboard '
|
||||
. $dashboard_kurzbz . ' not found.');
|
||||
$this->terminateWithJsonError('userconfig for dashboard ' . $dashboard_kurzbz . ' not found.');
|
||||
}
|
||||
|
||||
$override_decoded = json_decode($override->override, true);
|
||||
|
||||
if( !$this->DashboardLib->removeWidgetFromWidgets($override_decoded['widgets'],
|
||||
$funktion_kurzbz, $widgetid) )
|
||||
if (!$this->DashboardLib->removeWidgetFromWidgets($override_decoded['widgets'], $funktion_kurzbz, $widgetid))
|
||||
{
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError('widgetid ' . $widgetid . ' not found');
|
||||
@@ -163,15 +156,15 @@ class Config extends Auth_Controller
|
||||
|
||||
$override->override = json_encode($override_decoded);
|
||||
$result = $this->DashboardLib->insertOrUpdateOverride($override, $uid);
|
||||
if( isError($result) )
|
||||
if (isError($result))
|
||||
{
|
||||
http_response_code(500);
|
||||
$this->terminateWithJsonError('failed to remove widget');
|
||||
$this->terminateWithJsonError('failed to remove widget');
|
||||
}
|
||||
$this->outputJsonSuccess(array('msg' => 'override successfully updated.'));
|
||||
}
|
||||
|
||||
public function Funktionen()
|
||||
public function funktionen()
|
||||
{
|
||||
$funktionen = $this->FunktionModel->load();
|
||||
|
||||
@@ -185,7 +178,7 @@ class Config extends Auth_Controller
|
||||
return $this->outputJsonSuccess(getData($funktionen) ?: []);
|
||||
}
|
||||
|
||||
public function Preset()
|
||||
public function preset()
|
||||
{
|
||||
$db = $this->input->get('db');
|
||||
$funktion = $this->input->get('funktion');
|
||||
@@ -198,7 +191,7 @@ class Config extends Auth_Controller
|
||||
return $this->outputJsonSuccess(json_decode($conf->preset, true));
|
||||
}
|
||||
|
||||
public function PresetBatch()
|
||||
public function presetBatch()
|
||||
{
|
||||
$db = $this->input->get('db');
|
||||
$funktionen = $this->input->get('funktionen');
|
||||
@@ -220,5 +213,4 @@ class Config extends Auth_Controller
|
||||
|
||||
return $this->outputJsonSuccess($result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ class Dashboard extends Auth_Controller
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'dashboard/admin:r',
|
||||
'Create' => 'dashboard/admin:rw',
|
||||
'Update' => 'dashboard/admin:rw',
|
||||
'Delete' => 'dashboard/admin:rw'
|
||||
'create' => 'dashboard/admin:rw',
|
||||
'update' => 'dashboard/admin:rw',
|
||||
'delete' => 'dashboard/admin:rw'
|
||||
)
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ class Dashboard extends Auth_Controller
|
||||
$this->load->model('dashboard/Dashboard_model', 'DashboardModel');
|
||||
}
|
||||
|
||||
public function index()
|
||||
public function index()
|
||||
{
|
||||
$result = $this->DashboardModel->load();
|
||||
|
||||
@@ -36,7 +36,7 @@ class Dashboard extends Auth_Controller
|
||||
return $this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function Create()
|
||||
public function create()
|
||||
{
|
||||
$input = $this->getPostJSON();
|
||||
|
||||
@@ -52,7 +52,7 @@ class Dashboard extends Auth_Controller
|
||||
return $this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function Update()
|
||||
public function update()
|
||||
{
|
||||
$input = $this->getPostJSON();
|
||||
|
||||
@@ -68,7 +68,7 @@ class Dashboard extends Auth_Controller
|
||||
return $this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function Delete()
|
||||
public function delete()
|
||||
{
|
||||
$input = $this->getPostJSON();
|
||||
|
||||
@@ -83,5 +83,4 @@ class Dashboard extends Auth_Controller
|
||||
|
||||
return $this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class DashboardDemo extends Auth_Controller
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'user:r',
|
||||
'admin' => 'dashboard/admin:rw'
|
||||
)
|
||||
);
|
||||
|
||||
@@ -35,6 +36,13 @@ class DashboardDemo extends Auth_Controller
|
||||
$this->load->view('dashboard/dashboard_demo.php', []);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
public function admin()
|
||||
{
|
||||
$this->load->view('dashboard/dashboard_demo_admin.php', []);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
@@ -47,5 +55,4 @@ class DashboardDemo extends Auth_Controller
|
||||
|
||||
if (!$this->_uid) show_error('User authentification failed');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Widget extends Auth_Controller
|
||||
$this->load->model('dashboard/Dashboard_Widget_model', 'DashboardWidgetModel');
|
||||
}
|
||||
|
||||
public function index()
|
||||
public function index()
|
||||
{
|
||||
$widget_id = $this->input->get('id');
|
||||
|
||||
@@ -48,7 +48,7 @@ class Widget extends Auth_Controller
|
||||
return $this->outputJsonSuccess(current(getData($widget)));
|
||||
}
|
||||
|
||||
public function getAll()
|
||||
public function getAll()
|
||||
{
|
||||
$dashboard_id = $this->input->get('dashboard_id');
|
||||
$result = $this->WidgetModel->getWithAllowedForDashboard($dashboard_id);
|
||||
@@ -59,7 +59,7 @@ class Widget extends Auth_Controller
|
||||
$this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function getWidgetsForDashboard()
|
||||
public function getWidgetsForDashboard()
|
||||
{
|
||||
$db = $this->input->get('db');
|
||||
$result = $this->WidgetModel->getForDashboard($db);
|
||||
@@ -74,7 +74,8 @@ class Widget extends Auth_Controller
|
||||
$this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function setAllowed() {
|
||||
public function setAllowed()
|
||||
{
|
||||
$input = $this->getPostJSON();
|
||||
|
||||
$dashboard_id = $input->dashboard_id;
|
||||
@@ -83,12 +84,12 @@ class Widget extends Auth_Controller
|
||||
|
||||
if ($action == 'add') {
|
||||
$result = $this->DashboardWidgetModel->insert([
|
||||
'dashboard_id' => $dashboard_id,
|
||||
'dashboard_id' => $dashboard_id,
|
||||
'widget_id' => $widget_id
|
||||
]);
|
||||
} elseif ($action == 'delete') {
|
||||
$result = $this->DashboardWidgetModel->delete([
|
||||
'dashboard_id' => $dashboard_id,
|
||||
'dashboard_id' => $dashboard_id,
|
||||
'widget_id' => $widget_id
|
||||
]);
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
defined('BASEPATH') || exit('No direct script access allowed');
|
||||
|
||||
use \stdClass as stdClass;
|
||||
|
||||
/**
|
||||
* Description of DashboardLib
|
||||
*
|
||||
@@ -14,7 +17,7 @@ class DashboardLib
|
||||
|
||||
private $_ci; // CI instance
|
||||
|
||||
public function __construct($params=null)
|
||||
public function __construct()
|
||||
{
|
||||
// Loads CI instance
|
||||
$this->_ci =& get_instance();
|
||||
@@ -24,28 +27,27 @@ class DashboardLib
|
||||
$this->_ci->load->model('dashboard/Dashboard_Override_model', 'DashboardOverrideModel');
|
||||
}
|
||||
|
||||
public function generateWidgetId($dashboard_kurzbz='')
|
||||
public function generateWidgetId($dashboard_kurzbz = '')
|
||||
{
|
||||
$dashboard_kurzbz = (!empty($dashboard_kurzbz)) ? $dashboard_kurzbz
|
||||
: self::DEFAULT_DASHBOARD_KURZBZ;
|
||||
$widgetid_input = time() . '_' . $dashboard_kurzbz . '_'
|
||||
. bin2hex(random_bytes(self::WIDGET_ID_RANDOM_BYTES));
|
||||
$dashboard_kurzbz = (!empty($dashboard_kurzbz)) ? $dashboard_kurzbz : self::DEFAULT_DASHBOARD_KURZBZ;
|
||||
$widgetid_input = time() . '_' . $dashboard_kurzbz . '_' . bin2hex(random_bytes(self::WIDGET_ID_RANDOM_BYTES));
|
||||
$widgetid = md5($widgetid_input);
|
||||
return $widgetid;
|
||||
}
|
||||
|
||||
public function getDashboardByKurzbz($dashboard_kurzbz)
|
||||
public function getDashboardByKurzbz($dashboard_kurzbz)
|
||||
{
|
||||
$dashboard = null;
|
||||
$result = $this->_ci->DashboardModel->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
if( isSuccess($result) && ($dashboards = getData($result)) )
|
||||
|
||||
if (hasData($result))
|
||||
{
|
||||
$dashboard = $dashboards[0];
|
||||
return current(getData($result));
|
||||
}
|
||||
return $dashboard;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getMergedConfig($dashboard_id, $uid)
|
||||
public function getMergedConfig($dashboard_id, $uid)
|
||||
{
|
||||
$defaultconfig = $this->getDefaultConfig($dashboard_id, $uid);
|
||||
$userconfig = $this->getUserConfig($dashboard_id, $uid);
|
||||
@@ -60,15 +62,15 @@ class DashboardLib
|
||||
$res_presets = $this->_ci->DashboardPresetModel->getPresets($dashboard_id, $uid);
|
||||
$defaultconfig = array();
|
||||
|
||||
if( isSuccess($res_presets) && hasData($res_presets) )
|
||||
if (hasData($res_presets))
|
||||
{
|
||||
$presets = getData($res_presets);
|
||||
foreach ($presets as $presetobj)
|
||||
{
|
||||
if( null !== ($preset = json_decode($presetobj->preset, true)) )
|
||||
$preset = json_decode($presetobj->preset, true);
|
||||
if (null !== $preset)
|
||||
{
|
||||
$defaultconfig = array_replace_recursive($defaultconfig,
|
||||
$preset);
|
||||
$defaultconfig = array_replace_recursive($defaultconfig, $preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,25 +81,25 @@ class DashboardLib
|
||||
public function getUserConfig($dashboard_id, $uid)
|
||||
{
|
||||
$res_userconfig = $this->_ci->DashboardOverrideModel->getOverride($dashboard_id, $uid);
|
||||
$userconfig = array();
|
||||
|
||||
if( isSuccess($res_userconfig) && hasData($res_userconfig) )
|
||||
if (hasData($res_userconfig))
|
||||
{
|
||||
$data = getData($res_userconfig);
|
||||
if( null !== ($decodedconfig = json_decode($data[0]->override, true)) )
|
||||
$decodedconfig = json_decode(current($data)->override, true);
|
||||
if (null !== $decodedconfig)
|
||||
{
|
||||
$userconfig = $decodedconfig;
|
||||
return $decodedconfig;
|
||||
}
|
||||
}
|
||||
|
||||
return $userconfig;
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid)
|
||||
public function getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid)
|
||||
{
|
||||
$override = $this->getOverride($dashboard_kurzbz, $uid);
|
||||
if( null !== $override ) {
|
||||
return $override;
|
||||
if (null !== $override) {
|
||||
return $override;
|
||||
}
|
||||
|
||||
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
@@ -110,11 +112,13 @@ class DashboardLib
|
||||
return $emptyoverride;
|
||||
}
|
||||
|
||||
public function getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz)
|
||||
public function getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz)
|
||||
{
|
||||
if ($funktion_kurzbz === self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL)
|
||||
$funktion_kurzbz = null;
|
||||
$preset = $this->getPreset($dashboard_kurzbz, $funktion_kurzbz);
|
||||
if( null !== $preset ) {
|
||||
return $preset;
|
||||
if (null !== $preset) {
|
||||
return $preset;
|
||||
}
|
||||
|
||||
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
@@ -123,50 +127,49 @@ class DashboardLib
|
||||
$emptypreset->dashboard_id = $dashboard->dashboard_id;
|
||||
$emptypreset->funktion_kurzbz = $funktion_kurzbz;
|
||||
$section = ($funktion_kurzbz !== null) ? $funktion_kurzbz : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
|
||||
$emptypreset->preset = '{"widgets": {"' . $funktion_kurzbz . '": {}}}';
|
||||
$emptypreset->preset = '{"widgets": {"' . $section . '": {}}}';
|
||||
|
||||
return $emptypreset;
|
||||
}
|
||||
|
||||
public function getPreset($dashboard_kurzbz, $section)
|
||||
public function getPreset($dashboard_kurzbz, $section)
|
||||
{
|
||||
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
$preset = null;
|
||||
|
||||
$funktion_kurzbz = ($section === self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL) ? null : $section;
|
||||
$result = $this->_ci->DashboardPresetModel
|
||||
->getPresetByDashboardAndFunktion($dashboard->dashboard_id, $funktion_kurzbz);
|
||||
|
||||
if( isSuccess($result) && hasData($result) && ($presets = getData($result)) )
|
||||
if (hasData($result))
|
||||
{
|
||||
$preset = $presets[0];
|
||||
return current(getData($result));
|
||||
}
|
||||
|
||||
return $preset;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getOverride($dashboard_kurzbz, $uid)
|
||||
public function getOverride($dashboard_kurzbz, $uid)
|
||||
{
|
||||
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
$override = null;
|
||||
|
||||
$result = $this->_ci->DashboardOverrideModel
|
||||
->getOverride($dashboard->dashboard_id, $uid);
|
||||
|
||||
if( isSuccess($result) && hasData($result) && ($overrides = getData($result)) )
|
||||
if (hasData($result))
|
||||
{
|
||||
$override = $overrides[0];
|
||||
return current(getData($result));
|
||||
}
|
||||
|
||||
return $override;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function insertOrUpdatePreset($preset)
|
||||
public function insertOrUpdatePreset($preset)
|
||||
{
|
||||
if( isset($preset->preset_id) && $preset->preset_id > 0 )
|
||||
if (isset($preset->preset_id) && $preset->preset_id > 0)
|
||||
{
|
||||
$result = $this->_ci->DashboardPresetModel->update($preset->preset_id, $preset);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = $this->_ci->DashboardPresetModel->insert($preset);
|
||||
}
|
||||
@@ -174,12 +177,13 @@ class DashboardLib
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function insertOrUpdateOverride($override)
|
||||
public function insertOrUpdateOverride($override)
|
||||
{
|
||||
if( isset($override->override_id) && $override->override_id > 0 )
|
||||
if (isset($override->override_id) && $override->override_id > 0)
|
||||
{
|
||||
$result = $this->_ci->DashboardOverrideModel->update($override->override_id, $override);
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = $this->_ci->DashboardOverrideModel->insert($override);
|
||||
}
|
||||
@@ -199,10 +203,10 @@ class DashboardLib
|
||||
}
|
||||
}
|
||||
|
||||
public function addWidgetToWidgets(&$widgets, $section, $widget, $widgetid)
|
||||
public function addWidgetToWidgets(&$widgets, $section, $widget, $widgetid)
|
||||
{
|
||||
$section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
|
||||
if( !isset($widgets[$section]) || !is_array($widgets[$section]) )
|
||||
if (!isset($widgets[$section]) || !is_array($widgets[$section]))
|
||||
{
|
||||
$widgets[$section] = array();
|
||||
}
|
||||
@@ -210,10 +214,10 @@ class DashboardLib
|
||||
$widgets[$section][$widgetid] = $widget;
|
||||
}
|
||||
|
||||
public function removeWidgetFromWidgets(&$widgets, $section, $widgetid)
|
||||
public function removeWidgetFromWidgets(&$widgets, $section, $widgetid)
|
||||
{
|
||||
$section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
|
||||
if(isset($widgets[$section]) && isset($widgets[$section][$widgetid]) )
|
||||
$section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
|
||||
if (isset($widgets[$section]) && isset($widgets[$section][$widgetid]))
|
||||
{
|
||||
unset($widgets[$section][$widgetid]);
|
||||
if(empty($widgets[$section]) && $section !== self::USEROVERRIDE_SECTION) {
|
||||
|
||||
@@ -60,7 +60,7 @@ EOSQL;
|
||||
* @param string funktion_kurzbz
|
||||
* @return array
|
||||
*/
|
||||
public function getPresetByDashboardAndFunktion($dashboard_id, $funktion_kurzbz)
|
||||
public function getPresetByDashboardAndFunktion($dashboard_id, $funktion_kurzbz)
|
||||
{
|
||||
return $this->loadWhere(array('dashboard_id' => $dashboard_id, 'funktion_kurzbz' => $funktion_kurzbz));
|
||||
}
|
||||
|
||||
@@ -12,5 +12,4 @@ class Dashboard_Widget_model extends DB_Model
|
||||
$this->pk = ['dashboard_id', 'widget_id'];
|
||||
$this->hasSequence = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,5 +29,4 @@ class Widget_model extends DB_Model
|
||||
|
||||
return $this->loadWhere(['dashboard_kurzbz' => $db]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
$this->load->view('templates/FHC-Header',
|
||||
$this->load->view(
|
||||
'templates/FHC-Header',
|
||||
array(
|
||||
'title' => 'FH-Complete',
|
||||
'bootstrap5' => true,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
$this->load->view(
|
||||
'templates/FHC-Header',
|
||||
array(
|
||||
'title' => 'FH-Complete',
|
||||
'bootstrap5' => true,
|
||||
'fontawesome6' => true,
|
||||
'axios027' => true,
|
||||
'restclient' => true,
|
||||
'vue3' => true,
|
||||
'customJSModules' => ['public/js/apps/DashboardAdmin.js'],
|
||||
'customCSSs' => [
|
||||
'public/css/components/dashboard.css'
|
||||
],
|
||||
'navigationcomponent' => true
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<core-navigation-cmpt :add-side-menu-entries="appSideMenuEntries"></core-navigation-cmpt>
|
||||
|
||||
<div id="content">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Dashboard</h1>
|
||||
</div>
|
||||
<dashboard-admin dashboard="CIS" apiurl="<?= site_url('dashboard'); ?>"></dashboard-admin>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $this->load->view('templates/FHC-Footer'); ?>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
$this->load->view('templates/FHC-Header',
|
||||
$this->load->view(
|
||||
'templates/FHC-Header',
|
||||
array(
|
||||
'title' => 'FH-Complete',
|
||||
'bootstrap5' => true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
$this->load->view('templates/FHC-Header',
|
||||
$this->load->view(
|
||||
'templates/FHC-Header',
|
||||
array(
|
||||
'title' => 'FH-Complete',
|
||||
'bootstrap5' => true,
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
@import './calendar.css';
|
||||
.empty-tile-hover {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-500 -500 1448 1512"><path fill="lightgray" d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM200 344V280H136c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V168c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H248v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z"/></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.alert-danger .form-check-input:checked {
|
||||
border-color: #842029;
|
||||
background-color: #842029;
|
||||
}
|
||||
|
||||
.draganddropcontainer {
|
||||
grid-template-columns:repeat(4,1fr);
|
||||
gap: 1rem;
|
||||
place-items: stretch;
|
||||
place-content: stretch;
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 4;
|
||||
}
|
||||
@media(max-width: 577px) {
|
||||
.draganddropcontainer {
|
||||
grid-template-columns:repeat(2,1fr);
|
||||
:root {
|
||||
--fhc-dashboard-grid-size: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +32,4 @@
|
||||
}
|
||||
.cursor-move {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,5 @@ Vue.createApp({
|
||||
components: {
|
||||
CoreNavigationCmpt,
|
||||
CoreDashboard
|
||||
/*,
|
||||
"CoreFilterCmpt": CoreFilterCmpt,
|
||||
"verticalsplit": verticalsplit,
|
||||
"searchbar": searchbar*/
|
||||
}
|
||||
}).mount('#main');
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
|
||||
import CoreDashboard from '../components/Dashboard/Dashboard.js';
|
||||
import DashboardAdmin from '../components/Dashboard/Admin.js';
|
||||
|
||||
Vue.createApp({
|
||||
@@ -8,11 +7,7 @@ Vue.createApp({
|
||||
}),
|
||||
components: {
|
||||
CoreNavigationCmpt,
|
||||
DashboardAdmin,
|
||||
CoreDashboard/*,
|
||||
"CoreFilterCmpt": CoreFilterCmpt,
|
||||
"verticalsplit": verticalsplit,
|
||||
"searchbar": searchbar*/
|
||||
DashboardAdmin
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
BsPrompt.popup('New Dashboard name').then(
|
||||
name => {
|
||||
_name = name;
|
||||
return axios.post(this.apiurl + '/Dashboard/Create', {
|
||||
return axios.post(this.apiurl + '/Dashboard/create', {
|
||||
dashboard_kurzbz: name
|
||||
})
|
||||
}
|
||||
@@ -51,14 +51,14 @@ export default {
|
||||
},
|
||||
dashboardUpdate(dashboard) {
|
||||
// TODO(chris): Loading or message
|
||||
axios.post(this.apiurl + '/Dashboard/Update', dashboard).then(() => {
|
||||
axios.post(this.apiurl + '/Dashboard/update', dashboard).then(() => {
|
||||
let old = this.dashboards.find(el => el.dashboard_id == dashboard.dashboard_id);
|
||||
old.dashboard_kurzbz = dashboard.dashboard_kurzbz;
|
||||
old.beschreibung = dashboard.beschreibung;
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
},
|
||||
dashboardDelete(dashboard_id) {
|
||||
axios.post(this.apiurl + '/Dashboard/Delete', {dashboard_id}).then(() => {
|
||||
axios.post(this.apiurl + '/Dashboard/delete', {dashboard_id}).then(() => {
|
||||
this.current = -1;
|
||||
this.dashboards = this.dashboards.filter(el => el.dashboard_id != dashboard_id);
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
|
||||
@@ -29,6 +29,7 @@ export default {
|
||||
this.$refs.widgetpicker.getWidget().then(widget_id => {
|
||||
widget.widget = widget_id;
|
||||
delete widget.custom;
|
||||
widget.preset = 1;
|
||||
let loading = {...widget};
|
||||
loading.loading = true;
|
||||
this.sections.forEach(section => {
|
||||
@@ -119,7 +120,7 @@ export default {
|
||||
let funktionen = Array.from(evt.target.querySelectorAll("option:checked"),e=>e.value);
|
||||
this.sections = [];
|
||||
this.tmpLoading = funktionen.join('###');
|
||||
axios.get(this.apiurl + '/Config/PresetBatch', {params: {
|
||||
axios.get(this.apiurl + '/Config/presetBatch', {params: {
|
||||
db: this.dashboard,
|
||||
funktionen
|
||||
}}).then(res => {
|
||||
@@ -142,7 +143,7 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios.get(this.apiurl + '/Config/Funktionen').then(res => {
|
||||
axios.get(this.apiurl + '/Config/funktionen').then(res => {
|
||||
//console.log(res.data.retval);
|
||||
this.funktionen = {general: 'GENERAL'};
|
||||
res.data.retval.forEach(funktion => {
|
||||
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
if (this.sections[i].widgets[wid].id == k) {
|
||||
payload[k] = ObjectUtils.mergeDeep(this.sections[i].widgets[wid], payload[k]);
|
||||
// NOTE(chris): remove internal props
|
||||
for (var prop in {_x:1,_y:1,_w:1,_h:1,index:1,id:1})
|
||||
for (var prop in {_x:1,_y:1,_w:1,_h:1,index:1,id:1,preset:1})
|
||||
if (payload[k][prop])
|
||||
delete payload[k][prop];
|
||||
break;
|
||||
@@ -126,14 +126,19 @@ export default {
|
||||
//console.log(res.data.retval);
|
||||
for (var name in res.data.retval.widgets) {
|
||||
let widgets = [];
|
||||
let remove = [];
|
||||
for (var wid in res.data.retval.widgets[name]) {
|
||||
res.data.retval.widgets[name][wid].id = wid;
|
||||
widgets.push(res.data.retval.widgets[name][wid]);
|
||||
if (res.data.retval.widgets[name][wid].custom || res.data.retval.widgets[name][wid].preset)
|
||||
widgets.push(res.data.retval.widgets[name][wid]);
|
||||
else
|
||||
remove.push(wid);
|
||||
}
|
||||
this.sections.push({
|
||||
name: name,
|
||||
widgets: widgets
|
||||
});
|
||||
remove.forEach(wid => this.widgetRemove(name, wid));
|
||||
}
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
},
|
||||
|
||||
@@ -97,14 +97,15 @@ export default {
|
||||
this.arguments = {...this.widget.arguments, ...this.config};
|
||||
this.tmpConfig = {...this.arguments};
|
||||
},
|
||||
template: `<div v-if="loading">
|
||||
template: `
|
||||
<div v-if="loading">
|
||||
<div class="d-flex justify-content-center align-items-center h-100">
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!hidden || editMode" class="dashboard-item card overflow-hidden" :class="arguments ? arguments.className : ''" @mousedown="mouseDown($event)" @dragstart="startDrag($event)" :draggable="!!editMode">
|
||||
<div v-else-if="!hidden || editMode" class="dashboard-item card overflow-hidden h-100" :class="arguments && arguments.className ? arguments.className : ''">
|
||||
<div v-if="editMode && widget" class="card-header d-flex ps-0 pe-2">
|
||||
<span ref="dragHandle" class="col-auto mx-2 px-2 cursor-move"><i class="fa-solid fa-grip-vertical"></i></span>
|
||||
<span drag-action="move" class="col-auto mx-2 px-2 cursor-move"><i class="fa-solid fa-grip-vertical"></i></span>
|
||||
<span class="col">{{ widget.setup.name }}</span>
|
||||
<a v-if="hasConfig" class="col-auto px-1" href="#" @click.prevent="openConfig"><i class="fa-solid fa-gear"></i></a>
|
||||
<a v-if="custom" class="col-auto px-1" href="#" @click.prevent="$emit('remove')">
|
||||
@@ -114,7 +115,7 @@ export default {
|
||||
<input class="form-check-input ms-0" type="checkbox" role="switch" id="flexSwitchCheckChecked" :checked="!hidden" @input="$emit('remove', hidden)">
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="ready" class="card-body overflow-hidden" :class="arguments ? arguments.bodyClass : ''">
|
||||
<div v-if="ready" class="card-body overflow-hidden">
|
||||
<component :is="component" :config="arguments" :width="width" :height="height" @setConfig="setConfig" @change="changeConfigManually"></component>
|
||||
</div>
|
||||
<div v-else class="card-body overflow-hidden text-center d-flex flex-column justify-content-center"><i class="fa-solid fa-spinner fa-pulse fa-3x"></i></div>
|
||||
@@ -132,7 +133,7 @@ export default {
|
||||
</template>
|
||||
</bs-modal>
|
||||
<div v-if="editMode && isResizeable" class="card-footer d-flex justify-content-end p-0">
|
||||
<span ref="resizeHandle" class="col-auto px-1 cursor-nw-resize" @dragstart.prevent="$emit('resize')"><i class="fa-solid fa-up-right-and-down-left-from-center mirror-x"></i></span>
|
||||
<span drag-action="resize" class="col-auto px-1 cursor-nw-resize"><i class="fa-solid fa-up-right-and-down-left-from-center mirror-x"></i></span>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import BsConfirm from "../Bootstrap/Confirm.js";
|
||||
import DropGrid from '../Drop/Grid.js'
|
||||
import DashboardItem from "./Item.js";
|
||||
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
|
||||
|
||||
// TODO(chris): handle overflow (moving outside the box)
|
||||
export default {
|
||||
components: {
|
||||
DropGrid,
|
||||
DashboardItem
|
||||
},
|
||||
inject: {
|
||||
adminMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
@@ -23,371 +25,43 @@ export default {
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
gridWidth: 0,
|
||||
changeHeight: 1,
|
||||
movedObjects: [],
|
||||
editMode: this.adminMode ? 1 : 0,
|
||||
gridXLast: -1, // NOTE(chris): 0 based
|
||||
gridYLast: -1,
|
||||
dragging: 0,
|
||||
dataTransfer: {},
|
||||
gridAddFound: false,
|
||||
gridXAdd: -1, // NOTE(chris): 0 based
|
||||
gridYAdd: -1
|
||||
gridWidth: 1,
|
||||
editMode: this.adminMode
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
items() {
|
||||
this.widgets.forEach((item,i) => item.index = i);
|
||||
return this.widgets;
|
||||
},
|
||||
itemCoords() { // NOTE(chris): 1 based
|
||||
if (!this.gridWidth)
|
||||
return [];
|
||||
let itemCoords = this.items.map(item => item.place[this.gridWidth] || this.createItemPlacement(item));
|
||||
// TODO(chris): verify positions & sizes
|
||||
let occupiers = [];
|
||||
let wrongPlacedItems = [];
|
||||
let gridWidth = this.gridWidth;
|
||||
this.items.forEach(item => {
|
||||
let x = item._x !== undefined ? item._x : itemCoords[item.index].x;
|
||||
let y = item._y !== undefined ? item._y : itemCoords[item.index].y;
|
||||
let w = item._w !== undefined ? item._w : itemCoords[item.index].w;
|
||||
let h = item._h !== undefined ? item._h : itemCoords[item.index].h;
|
||||
// TODO(chris): check with and height params here?
|
||||
for (var i = 0; i < w; i++) {
|
||||
for (var j = 0; j < h; j++) {
|
||||
var c = (y+j-1) * gridWidth + (x+i-1);
|
||||
// NOTE(chris): check for overlaping items
|
||||
if (occupiers[c] !== undefined) {
|
||||
//console.log('try to add ' + item.index + ' to ' + x + '/' + y + ', but ' + occupiers[c] + ' is already there');
|
||||
// NOTE(chris): remove possible other entries of this item
|
||||
for (var c2 = c; c2; c2--)
|
||||
if (occupiers[c2] == item.index)
|
||||
occupiers[c2] = undefined;
|
||||
wrongPlacedItems.push(item);
|
||||
return;
|
||||
}
|
||||
occupiers[c] = item.index;
|
||||
}
|
||||
}
|
||||
return this.widgets.map(item => {
|
||||
return {...item, ...(item.place[this.gridWidth] || {})};
|
||||
});
|
||||
wrongPlacedItems.forEach(item => {
|
||||
let w = item._w !== undefined ? item._w : itemCoords[item.index].w;
|
||||
let h = item._h !== undefined ? item._h : itemCoords[item.index].h;
|
||||
for (var c = 0; c < occupiers.length + gridWidth; c++) {
|
||||
if (occupiers[c] === undefined) {
|
||||
var occupied = false, i, j;
|
||||
for (i = 0; i < w; i++) {
|
||||
for (j = 0; j < h; j++) {
|
||||
if (occupiers[c + i + j * gridWidth] !== undefined) {
|
||||
i = w;
|
||||
occupied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!occupied) {
|
||||
item.place[gridWidth].x = c%gridWidth + 1;
|
||||
item.place[gridWidth].y = Math.floor(c/gridWidth) + 1;
|
||||
for (i = 0; i < w; i++) {
|
||||
for (j = 0; j < h; j++) {
|
||||
occupiers[c + i + j * gridWidth] = item.index;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return itemCoords;
|
||||
},
|
||||
gridHeight() {
|
||||
if (!this.gridWidth || !this.changeHeight)
|
||||
return 0;
|
||||
let minH = 0;
|
||||
this.itemCoords.forEach((item,i) => minH = Math.max(minH, (!this.editMode && this.items[i].hidden) ? 0 : item.y - 1 + item.h));
|
||||
// TODO(chris): the extraline should only be present if all slots are occupied
|
||||
if (minH == 0 && this.editMode)
|
||||
return 1;
|
||||
return minH + this.editMode*this.dragging;
|
||||
},
|
||||
gridOccupiers() { // NOTE(chris): 0 based
|
||||
let occupiers = [];
|
||||
let gridWidth = this.gridWidth;
|
||||
this.items.forEach(item => {
|
||||
let x = item._x !== undefined ? item._x : this.itemCoords[item.index].x;
|
||||
let y = item._y !== undefined ? item._y : this.itemCoords[item.index].y;
|
||||
let w = item._w !== undefined ? item._w : this.itemCoords[item.index].w;
|
||||
let h = item._h !== undefined ? item._h : this.itemCoords[item.index].h;
|
||||
for (var i = 0; i < w; i++) {
|
||||
for (var j = 0; j < h; j++) {
|
||||
var c = (y+j-1) * gridWidth + (x+i-1);
|
||||
occupiers[c] = item.index;
|
||||
}
|
||||
}
|
||||
});
|
||||
return occupiers;
|
||||
},
|
||||
cssBg() {
|
||||
if (!this.editMode || this.dragging || !this.gridAddFound)
|
||||
return 'transparent';
|
||||
let x = this.gridXAdd, y = this.gridYAdd, h = this.gridHeight-1 || 1, w = this.gridWidth-1 || 1;
|
||||
return `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-500 -500 1448 1512"><path fill="lightgray" d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM200 344V280H136c-13.3 0-24-10.7-24-24s10.7-24 24-24h64V168c0-13.3 10.7-24 24-24s24 10.7 24 24v64h64c13.3 0 24 10.7 24 24s-10.7 24-24 24H248v64c0 13.3-10.7 24-24 24s-24-10.7-24-24z"/></svg>')` + (100 * x/w) + '% ' + (100 * y/h) + '%/' + (100/this.gridWidth) + '% ' + (100/this.gridHeight) + '% no-repeat;cursor:pointer';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addWidget(evt) {
|
||||
if (evt.target != this.$refs.container || !this.editMode)
|
||||
return;
|
||||
const rect = this.$refs.container.getBoundingClientRect();
|
||||
const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
|
||||
const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
|
||||
if (this.gridOccupiers[gridY * this.gridWidth + gridX] === undefined) {
|
||||
let widget = { widget: 1, config: {}, place: {}, custom: 1 };
|
||||
widget.place[this.gridWidth] = {
|
||||
x: gridX+1,
|
||||
y: gridY+1,
|
||||
w: 1,
|
||||
h: 1
|
||||
};
|
||||
this.$emit('widgetAdd', this.name, widget);
|
||||
checkResizeLimit(item, w, h) {
|
||||
// NOTE(chris): widgets needs to be loaded for this to work
|
||||
let widget = CachedWidgetLoader.getWidget(item.widget);
|
||||
if (widget) {
|
||||
let minmaxW = widget.setup.width;
|
||||
if (minmaxW.max)
|
||||
minmaxW.min = minmaxW.min || 1;
|
||||
else
|
||||
minmaxW = {min:minmaxW,max:minmaxW};
|
||||
if (w < minmaxW.min)
|
||||
w = minmaxW.min;
|
||||
if (w > minmaxW.max)
|
||||
w = minmaxW.max;
|
||||
|
||||
let minmaxH = widget.setup.height;
|
||||
if (minmaxH.max)
|
||||
minmaxH.min = minmaxH.min || 1;
|
||||
else
|
||||
minmaxH = {min:minmaxH,max:minmaxH};
|
||||
if (h < minmaxH.min)
|
||||
h = minmaxH.min;
|
||||
if (h > minmaxH.max)
|
||||
h = minmaxH.max;
|
||||
}
|
||||
},
|
||||
createItemPlacement(item) {
|
||||
// TODO(chris): create correct default placement if it is not there
|
||||
item.place[this.gridWidth] = {x:1,y:1,w:1,h:1};
|
||||
/*var freeList = [], nextId = 0;
|
||||
this.items.forEach(item => {
|
||||
if (!item.place[this.gridWidth]) {
|
||||
if (!this.gridWidth) {
|
||||
item.place[this.gridWidth] = {x:1,y:nextId++,w:1,h:1};
|
||||
} else {
|
||||
// TODO(chris): IMPLEMENT widths & heights
|
||||
if (freeList[nextId])
|
||||
while (freeList[++nextId]);
|
||||
freeList[nextId] = 1;
|
||||
item.place[this.gridWidth] = {x:(nextId%this.gridWidth)+1,y:Math.floor(nextId/this.gridWidth)+1,w:1,h:1};
|
||||
}
|
||||
}
|
||||
});*/
|
||||
return item.place[this.gridWidth];
|
||||
},
|
||||
onMouseMove(evt) {
|
||||
if (!this.editMode || this.dragging) {
|
||||
this.gridXAdd = this.gridYAdd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = this.$refs.container.getBoundingClientRect();
|
||||
const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
|
||||
const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
|
||||
|
||||
if (this.gridXAdd == gridX && this.gridYAdd == gridY)
|
||||
return;
|
||||
// TODO(chris): only mark it when its not occupied
|
||||
this.gridXAdd = gridX;
|
||||
this.gridYAdd = gridY;
|
||||
this.gridAddFound = (this.gridOccupiers[gridX + gridY * this.gridWidth] === undefined);
|
||||
},
|
||||
onMouseLeave() {
|
||||
this.gridXAdd = this.gridYAdd = -1;
|
||||
this.gridAddFound = false;
|
||||
},
|
||||
startDrag(evt, item) {
|
||||
this.dragging = 1;
|
||||
this.gridXLast = -1;
|
||||
this.gridYLast = -1;
|
||||
item._x = this.itemCoords[item.index].x;
|
||||
item._y = this.itemCoords[item.index].y;
|
||||
|
||||
evt.dataTransfer.dropEffect = 'move';
|
||||
evt.dataTransfer.effectAllowed = 'move';
|
||||
this.dataTransfer = {
|
||||
action: 'm',
|
||||
id: item.index,
|
||||
w: this.itemCoords[item.index].w,
|
||||
h: this.itemCoords[item.index].h
|
||||
}
|
||||
},
|
||||
startResize(evt, item) {
|
||||
this.dragging = 1;
|
||||
this.gridXLast = -1;
|
||||
this.gridYLast = -1;
|
||||
item._w = this.itemCoords[item.index].w;
|
||||
item._h = this.itemCoords[item.index].h;
|
||||
|
||||
evt.dataTransfer.setDragImage(evt.target, -99999, -99999);
|
||||
evt.dataTransfer.dropEffect = 'move';
|
||||
evt.dataTransfer.effectAllowed = 'move';
|
||||
this.dataTransfer = {
|
||||
action: 'r',
|
||||
id: item.index,
|
||||
x: this.itemCoords[item.index].x,
|
||||
y: this.itemCoords[item.index].y
|
||||
}
|
||||
},
|
||||
occupyFields(id, x, y, w, h) {
|
||||
var c;
|
||||
while ((c = this.movedObjects.pop())) {
|
||||
if (this.items[c]._y !== undefined) {
|
||||
this.items[c].place[this.gridWidth].y = this.items[c]._y;
|
||||
this.items[c]._y = undefined;
|
||||
}
|
||||
}
|
||||
var move = {};
|
||||
move[id] = this.items[id];
|
||||
this.getOccupiedItems(x,y,w,h,move);
|
||||
h = y + h;
|
||||
y = 0;
|
||||
for (x in move) {
|
||||
if (x != id) {
|
||||
c = move[x]._y !== undefined ? move[x]._y : this.itemCoords[x].y;
|
||||
if (c < h)
|
||||
y = Math.max(h-c, y);
|
||||
}
|
||||
}
|
||||
for (x in move) {
|
||||
if (x != id) {
|
||||
this.movedObjects.push(x);
|
||||
if (move[x]._y === undefined) {
|
||||
move[x]._y = this.itemCoords[x].y;
|
||||
}
|
||||
move[x].place[this.gridWidth].y = move[x]._y + y;
|
||||
}
|
||||
}
|
||||
},
|
||||
getOccupiedItems(x, y, w, h, move) {
|
||||
var i, j, c;
|
||||
for (i = 0; i < w; i++) {
|
||||
for (j = 0; j < h; j++) {
|
||||
c = (y+j-1) * this.gridWidth + (x+i-1);
|
||||
if (this.gridOccupiers[c] !== undefined && !move[this.gridOccupiers[c]]) {
|
||||
move[this.gridOccupiers[c]] = this.items[this.gridOccupiers[c]];
|
||||
c = this.itemCoords[this.gridOccupiers[c]];
|
||||
this.getOccupiedItems(c.x, c.y + 1, c.w, c.h, move);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onDragOver(evt) {
|
||||
let id, x, y, w, h;
|
||||
const action = this.dataTransfer.action;
|
||||
const rect = this.$refs.container.getBoundingClientRect();
|
||||
const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
|
||||
const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
|
||||
|
||||
if (this.gridXLast == gridX && this.gridYLast == gridY)
|
||||
return;
|
||||
this.gridXLast = gridX;
|
||||
this.gridYLast = gridY;
|
||||
|
||||
if (action == 'm') {
|
||||
x = Math.max(gridX + 1, 1);
|
||||
y = Math.max(gridY + 1, 1);
|
||||
w = parseInt(this.dataTransfer.w);
|
||||
h = parseInt(this.dataTransfer.h);
|
||||
|
||||
if (x + w > this.gridWidth + 1)
|
||||
x = this.gridWidth + 1 - w;
|
||||
|
||||
id = this.dataTransfer.id;
|
||||
this.occupyFields(id, x, y, w, h);
|
||||
|
||||
this.itemCoords[id].x = x;
|
||||
this.itemCoords[id].y = y;
|
||||
} else if (action == 'r') {
|
||||
x = parseInt(this.dataTransfer.x);
|
||||
y = parseInt(this.dataTransfer.y);
|
||||
w = gridX + 2 - x;
|
||||
h = gridY + 2 - y;
|
||||
w = Math.max(1, w);
|
||||
h = Math.max(1, h);
|
||||
|
||||
if (x + w > this.gridWidth + 1)
|
||||
w = this.gridWidth + 1 - x;
|
||||
|
||||
id = this.dataTransfer.id;
|
||||
let widget = CachedWidgetLoader.getWidget(this.items[id].widget);
|
||||
if (widget) {
|
||||
let minmaxW = widget.setup.width;
|
||||
if (minmaxW.max)
|
||||
minmaxW.min = minmaxW.min || 1;
|
||||
else
|
||||
minmaxW = {min:minmaxW,max:minmaxW};
|
||||
if (w < minmaxW.min)
|
||||
w = minmaxW.min;
|
||||
if (w > minmaxW.max)
|
||||
w = minmaxW.max;
|
||||
|
||||
let minmaxH = widget.setup.height;
|
||||
if (minmaxH.max)
|
||||
minmaxH.min = minmaxH.min || 1;
|
||||
else
|
||||
minmaxH = {min:minmaxH,max:minmaxH};
|
||||
if (h < minmaxH.min)
|
||||
h = minmaxH.min;
|
||||
if (h > minmaxH.max)
|
||||
h = minmaxH.max;
|
||||
}
|
||||
|
||||
this.occupyFields(id, x, y, w, h);
|
||||
|
||||
this.itemCoords[id].w = w;
|
||||
this.itemCoords[id].h = h;
|
||||
}
|
||||
},
|
||||
onDrop() {
|
||||
this.dragging = 0;
|
||||
this.gridXLast = -1;
|
||||
this.gridYLast = -1;
|
||||
let id = 0;
|
||||
let update = {};
|
||||
while ((id = this.movedObjects.pop())) {
|
||||
if (this.items[id]._y !== undefined) {
|
||||
if (this.itemCoords[id].y != this.items[id]._y) {
|
||||
update[this.items[id].id] = {place:{}};
|
||||
update[this.items[id].id].place[this.gridWidth] = {y:this.itemCoords[id].y};
|
||||
}
|
||||
this.items[id]._y = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
id = this.dataTransfer.id;
|
||||
|
||||
const action = this.dataTransfer.action;
|
||||
update[this.items[id].id] = {place:{}};
|
||||
update[this.items[id].id].place[this.gridWidth] = {};
|
||||
|
||||
if (action == 'm') {
|
||||
if (this.items[id]._x !== undefined) {
|
||||
if (this.itemCoords[id].x != this.items[id]._x) {
|
||||
update[this.items[id].id].place[this.gridWidth].x = this.itemCoords[id].x;
|
||||
}
|
||||
this.items[id]._x = undefined;
|
||||
}
|
||||
if (this.items[id]._y !== undefined) {
|
||||
if (this.itemCoords[id].y != this.items[id]._y) {
|
||||
update[this.items[id].id].place[this.gridWidth].y = this.itemCoords[id].y;
|
||||
}
|
||||
this.items[id]._y = undefined;
|
||||
}
|
||||
} else if (action == 'r') {
|
||||
update[this.items[id].id].place[this.gridWidth].w = this.itemCoords[id].w;
|
||||
update[this.items[id].id].place[this.gridWidth].h = this.itemCoords[id].h;
|
||||
}
|
||||
|
||||
if (update[this.items[id].id].place[this.gridWidth].x === undefined &&
|
||||
update[this.items[id].id].place[this.gridWidth].y === undefined &&
|
||||
update[this.items[id].id].place[this.gridWidth].w === undefined &&
|
||||
update[this.items[id].id].place[this.gridWidth].h === undefined) {
|
||||
delete update[this.items[id].id].place[this.gridWidth];
|
||||
}
|
||||
|
||||
this.updatePreset(update);
|
||||
|
||||
// TODO(chris): find better way to trigger change for gridHeight
|
||||
this.changeHeight++
|
||||
return [w, h];
|
||||
},
|
||||
removeWidget(item, revert) {
|
||||
if (item.custom) {
|
||||
@@ -400,9 +74,32 @@ export default {
|
||||
},
|
||||
saveConfig(config, item) {
|
||||
let payload = {};
|
||||
payload[item.id] = { config };console.log(payload);
|
||||
payload[item.id] = { config };
|
||||
this.updatePreset(payload);
|
||||
},
|
||||
updatePositions(updated) {
|
||||
let result = {};
|
||||
updated.forEach(update => {
|
||||
let item = {...update.item};
|
||||
if (!item.place[this.gridWidth])
|
||||
item.place[this.gridWidth] = {x: 0, y: 0, w: 1, h: 1};
|
||||
delete item.x;
|
||||
delete item.y;
|
||||
delete item.w;
|
||||
delete item.h;
|
||||
if (update.x !== undefined)
|
||||
item.place[this.gridWidth].x = update.x;
|
||||
if (update.y !== undefined)
|
||||
item.place[this.gridWidth].y = update.y;
|
||||
if (update.w !== undefined)
|
||||
item.place[this.gridWidth].w = update.w;
|
||||
if (update.h !== undefined)
|
||||
item.place[this.gridWidth].h = update.h;
|
||||
|
||||
result[item.id] = item;
|
||||
});
|
||||
this.updatePreset(result);
|
||||
},
|
||||
updatePreset(update) {
|
||||
let payload = {};
|
||||
payload[this.name] = update;
|
||||
@@ -412,55 +109,34 @@ export default {
|
||||
mounted() {
|
||||
let self = this;
|
||||
let cont = self.$refs.container;
|
||||
self.gridWidth = window.getComputedStyle(cont).getPropertyValue('grid-template-columns').split(" ").length;
|
||||
self.gridWidth = parseInt(window.getComputedStyle(cont).getPropertyValue('--fhc-dashboard-grid-size'));
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
for (const child of cont.children) {
|
||||
child.style.display = 'none';
|
||||
}
|
||||
self.gridWidth = window.getComputedStyle(cont).getPropertyValue('grid-template-columns').split(" ").length;
|
||||
for (const child of cont.children) {
|
||||
child.style.display = '';
|
||||
}
|
||||
self.gridWidth = parseInt(window.getComputedStyle(cont).getPropertyValue('--fhc-dashboard-grid-size'));
|
||||
});
|
||||
},
|
||||
template: `<div class="dashboard-section">
|
||||
template: `
|
||||
<div class="dashboard-section" ref="container">
|
||||
<h3 class="d-flex">
|
||||
<span class="col">{{name}}</span>
|
||||
<button class="col-auto btn" @click.prevent="editMode = editMode ? 0 : 1"><i class="fa-solid fa-gear"></i></button>
|
||||
<button class="col-auto btn" @click.prevent="editMode = !editMode"><i class="fa-solid fa-gear"></i></button>
|
||||
</h3>
|
||||
<div :h="gridHeight" :w="gridWidth" class="position-relative" :style="'height:0;padding-bottom:' + (gridHeight * 100/gridWidth) + '%'">
|
||||
<div ref="container"
|
||||
class="position-absolute top-0 left-0 w-100 h-100 draganddropcontainer"
|
||||
:style="'display:grid;grid-template-rows:repeat('+gridHeight+',1fr);background:'+cssBg"
|
||||
@click="addWidget($event)"
|
||||
@drop="onDrop($event, 1)"
|
||||
@dragover.prevent="onDragOver"
|
||||
@dragenter.prevent
|
||||
@mousemove="onMouseMove"
|
||||
@mouseleave="onMouseLeave">
|
||||
|
||||
<dashboard-item
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:id="item.widget"
|
||||
<drop-grid v-model:cols="gridWidth" :items="items" :active="editMode" :resize-limit="checkResizeLimit" :margin-for-extra-row=".01" @rearrange-items="updatePositions">
|
||||
<template v-slot="item">
|
||||
<dashboard-item
|
||||
:id="item.widget"
|
||||
:loading="item.loading"
|
||||
:config="item.config"
|
||||
:custom="item.custom"
|
||||
:hidden="item.hidden"
|
||||
:editMode="editMode"
|
||||
:x="itemCoords[item.index] ? itemCoords[item.index].x : -1"
|
||||
:y="itemCoords[item.index] ? itemCoords[item.index].y : -1"
|
||||
:style="itemCoords[item.index] ? {'grid-column-start':itemCoords[item.index].x,'grid-column-end':itemCoords[item.index].x+itemCoords[item.index].w,'grid-row-start':itemCoords[item.index].y,'grid-row-end':itemCoords[item.index].y+itemCoords[item.index].h} : {}"
|
||||
:width="itemCoords[item.index] ? itemCoords[item.index].w : 0"
|
||||
:height="itemCoords[item.index] ? itemCoords[item.index].h : 0"
|
||||
@dragstart="startDrag($event, item)"
|
||||
@resizestart="startResize($event, item)"
|
||||
:config="item.config"
|
||||
:custom="item.custom"
|
||||
:hidden="item.hidden"
|
||||
:editMode="editMode"
|
||||
@change="saveConfig($event, item)"
|
||||
@remove="removeWidget(item, $event)">
|
||||
</dashboard-item>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #empty-tile-hover="{x,y}">
|
||||
<div class="empty-tile-hover" @click="$emit('widgetAdd', name, { widget: 1, config: {}, place: {[gridWidth]: {x,y,w:1,h:1}}, custom: 1 })"></div>
|
||||
</template>
|
||||
</drop-grid>
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
// TODO(chris): Comments
|
||||
|
||||
import GridItem from './Grid/Item.js';
|
||||
import GridLogic from '../../composables/GridLogic.js';
|
||||
|
||||
const MODE_IDLE = 0;
|
||||
const MODE_MOVE = 1;
|
||||
const MODE_RESIZE = 2;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GridItem
|
||||
},
|
||||
inject: {
|
||||
},
|
||||
props: {
|
||||
cols: Number,
|
||||
items: Array,
|
||||
resizeLimit: Function,
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
marginForExtraRow: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
"rearrangeItems",
|
||||
"newItem"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
x: -1,
|
||||
y: -1,
|
||||
mode: MODE_IDLE,
|
||||
grid: null,
|
||||
dragGrid: null,
|
||||
permUpdates: [],
|
||||
positionUpdates: null,
|
||||
fixedPositionUpdates: null,
|
||||
draggedOffset: [0,0],
|
||||
draggedItem: null,
|
||||
additionalRow: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rows() {
|
||||
if ((this.mode == MODE_MOVE || this.mode == MODE_RESIZE) && this.dragGrid)
|
||||
return this.dragGrid.h;
|
||||
return this.grid ? this.grid.h : 1;
|
||||
},
|
||||
gridStyle() {
|
||||
const addH = this.active ? this.marginForExtraRow : 0;
|
||||
return {
|
||||
'--fhc-dg-row-height': 100/(this.rows + addH) + '%',
|
||||
'--fhc-dg-col-width': 100/this.cols + '%',
|
||||
'padding-bottom': 100 * (this.rows + addH)/this.cols + '%'
|
||||
}
|
||||
},
|
||||
indexedItems() {
|
||||
return this.items.map(
|
||||
(item, index) => {
|
||||
return {
|
||||
index: index,
|
||||
x: item.x,
|
||||
y: item.y,
|
||||
w: item.w,
|
||||
h: item.h,
|
||||
weight: item.weight || 0,
|
||||
data: item
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
prePlacedItems() {
|
||||
if (!this.fixedPositionUpdates)
|
||||
return this.indexedItems;
|
||||
return this.indexedItems.map(item => {
|
||||
if (!this.fixedPositionUpdates[item.index])
|
||||
return item;
|
||||
return {
|
||||
index: item.index,
|
||||
weight: item.weight,
|
||||
data: item.data,
|
||||
x: this.fixedPositionUpdates[item.index].x === undefined ? item.x : this.fixedPositionUpdates[item.index].x,
|
||||
y: this.fixedPositionUpdates[item.index].y === undefined ? item.y : this.fixedPositionUpdates[item.index].y,
|
||||
w: this.fixedPositionUpdates[item.index].w === undefined ? item.w : this.fixedPositionUpdates[item.index].w,
|
||||
h: this.fixedPositionUpdates[item.index].h === undefined ? item.h : this.fixedPositionUpdates[item.index].h
|
||||
};
|
||||
});
|
||||
},
|
||||
placedItems() {
|
||||
if (!this.positionUpdates)
|
||||
return this.prePlacedItems;
|
||||
return this.prePlacedItems.map(item => {
|
||||
if (!this.positionUpdates[item.index])
|
||||
return item;
|
||||
return {
|
||||
index: item.index,
|
||||
weight: item.weight,
|
||||
data: item.data,
|
||||
x: this.positionUpdates[item.index].x === undefined ? item.x : this.positionUpdates[item.index].x,
|
||||
y: this.positionUpdates[item.index].y === undefined ? item.y : this.positionUpdates[item.index].y,
|
||||
w: this.positionUpdates[item.index].w === undefined ? item.w : this.positionUpdates[item.index].w,
|
||||
h: this.positionUpdates[item.index].h === undefined ? item.h : this.positionUpdates[item.index].h
|
||||
};
|
||||
});
|
||||
},
|
||||
showEmptyTileHover() {
|
||||
if (!this.active || !this.grid || this.mode != MODE_IDLE || this.x < 0 || this.y < 0 || this.x >= this.cols || this.y >= this.rows)
|
||||
return false;
|
||||
return this.grid.isFreeSlot(this.x, this.y);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
active(active) {
|
||||
if (!active)
|
||||
this.dragCancel();
|
||||
},
|
||||
cols() {
|
||||
this.dragCancel();
|
||||
},
|
||||
indexedItems: {
|
||||
handler(value) {
|
||||
this.dragCancel();
|
||||
|
||||
const updated = this.createNewGrid(value);
|
||||
|
||||
this.fixedPositionUpdates = updated;
|
||||
if (updated.length)
|
||||
this.$emit('rearrangeItems', updated.filter(v => v));
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createNewGrid(items) {
|
||||
this.grid = new GridLogic(this.cols);
|
||||
const result = [];
|
||||
[...items].sort((a, b) => a.weight > b.weight).forEach(item => {
|
||||
if (item.x + item.w > this.cols) {
|
||||
let targetW = this.cols-item.x,
|
||||
targetX = undefined;
|
||||
if (this.resizeLimit) {
|
||||
[targetW] = this.resizeLimit(item.data, targetW, item.h);
|
||||
}
|
||||
if (targetW < 1)
|
||||
targetW = 1;
|
||||
if (targetW > this.cols)
|
||||
targetW = this.cols;
|
||||
if (item.x + targetW > this.cols) {
|
||||
targetX = this.cols - targetW;
|
||||
}
|
||||
if (targetW == item.w)
|
||||
targetW = undefined;
|
||||
result[item.index] = {
|
||||
item: item.data,
|
||||
x: targetX,
|
||||
w: targetW
|
||||
};
|
||||
}
|
||||
item.frame = this.grid.getItemFrame(item);
|
||||
this.convertGridResultToUpdate(this.grid.add(item), result, items);
|
||||
});
|
||||
this.grid.clearWeights();
|
||||
return result;
|
||||
},
|
||||
convertGridResultToUpdate(input, output, baseArray) {
|
||||
if (!input)
|
||||
return;
|
||||
if (!baseArray)
|
||||
baseArray = this.indexedItems;
|
||||
input.forEach(item => {
|
||||
let result = {
|
||||
item: baseArray[item.index].data
|
||||
};
|
||||
if (item.x !== undefined)
|
||||
result.x = item.x;
|
||||
if (item.y !== undefined)
|
||||
result.y = item.y;
|
||||
if (item.w !== undefined)
|
||||
result.w = item.w;
|
||||
if (item.h !== undefined)
|
||||
result.h = item.h;
|
||||
output[item.index] = result;
|
||||
});
|
||||
},
|
||||
mouseLeave() {
|
||||
if (this.mode == MODE_IDLE) {
|
||||
this.x = -1;
|
||||
this.y = -1;
|
||||
if (this.additionalRow !== null) {
|
||||
this.grid.h = this.additionalRow;
|
||||
this.additionalRow = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCursor(evt) {
|
||||
if (!this.active) {
|
||||
this.x = this.y = -1;
|
||||
return false;
|
||||
}
|
||||
const addH = this.active ? this.marginForExtraRow : 0;
|
||||
const rect = this.$refs.container.getBoundingClientRect();
|
||||
|
||||
if (!evt.clientX && !evt.clientY && evt.touches) {
|
||||
evt.clientX = evt.touches[0].clientX;
|
||||
evt.clientY = evt.touches[0].clientY;
|
||||
}
|
||||
|
||||
const gridX = Math.floor(this.cols * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
|
||||
const gridY = Math.floor((this.rows + addH) * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
|
||||
if (this.x == gridX && this.y == gridY)
|
||||
return false;
|
||||
|
||||
if (this.mode == MODE_IDLE) {
|
||||
if (this.additionalRow === null && this.y == this.rows-1 && gridY == this.rows) {
|
||||
this.additionalRow = this.grid.h;
|
||||
this.grid.h += 1;
|
||||
} else if (this.additionalRow !== null && gridY != this.rows - 1) {
|
||||
this.grid.h = this.additionalRow;
|
||||
this.additionalRow = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.x = gridX;
|
||||
this.y = gridY;
|
||||
|
||||
return true;
|
||||
},
|
||||
_dragStart(evt) {
|
||||
if (evt.dataTransfer) {
|
||||
evt.dataTransfer.setDragImage(evt.target, -99999, -99999);
|
||||
evt.dataTransfer.dropEffect = 'move';
|
||||
evt.dataTransfer.effectAllowed = 'move';
|
||||
}
|
||||
},
|
||||
startMove(evt, item) {
|
||||
if (!this.active)
|
||||
return;
|
||||
this._dragStart(evt);
|
||||
this.mode = MODE_MOVE;
|
||||
this.updateCursor(evt);
|
||||
this.draggedItem = item;
|
||||
this.draggedOffset = [item.x - this.x, item.y - this.y];
|
||||
},
|
||||
startResize(evt, item) {
|
||||
if (!this.active)
|
||||
return;
|
||||
this._dragStart(evt);
|
||||
this.mode = MODE_RESIZE;
|
||||
this.draggedItem = item;
|
||||
},
|
||||
dragOver(evt) {
|
||||
if (!this.active)
|
||||
return this.dragCancel();
|
||||
if (this.updateCursor(evt)) {
|
||||
switch(this.mode) {
|
||||
case MODE_MOVE: {
|
||||
evt.preventDefault();
|
||||
this.dragGrid = new GridLogic(this.grid);
|
||||
let x = this.x + this.draggedOffset[0];
|
||||
let y = this.y + this.draggedOffset[1];
|
||||
if (x < 0) {
|
||||
this.draggedOffset[0] -= x;
|
||||
x = 0;
|
||||
} else if (x + this.draggedItem.w > this.cols) {
|
||||
this.draggedOffset[0] += this.cols - this.draggedItem.w - x;
|
||||
x = this.cols - this.draggedItem.w;
|
||||
}
|
||||
if (y < 0) {
|
||||
this.draggedOffset[1] -= y;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
this.positionUpdates = this.dragGrid.move(this.draggedItem, x, y);
|
||||
break;
|
||||
}
|
||||
case MODE_RESIZE: {
|
||||
evt.preventDefault();
|
||||
this.dragGrid = new GridLogic(this.grid);
|
||||
let w = Math.min(this.cols - this.draggedItem.x, Math.max(1, this.x - this.draggedItem.x + 1));
|
||||
let h = Math.max(1, this.y - this.draggedItem.y + 1);
|
||||
if (this.resizeLimit)
|
||||
[w, h] = this.resizeLimit(this.draggedItem.data, w, h);
|
||||
this.positionUpdates = this.dragGrid.resize(this.draggedItem, w, h);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dragCancel() {
|
||||
this.mode = MODE_IDLE;
|
||||
this.positionUpdates = null;
|
||||
this.draggedOffset = [0,0],
|
||||
this.draggedItem = null;
|
||||
},
|
||||
dragEnd() {
|
||||
if (this.mode == MODE_IDLE)
|
||||
return;
|
||||
if (!this.active || this.x < 0 || this.y < 0 || this.x >= this.cols)
|
||||
return this.dragCancel();
|
||||
this.mode = MODE_IDLE;
|
||||
let updated = [];
|
||||
this.convertGridResultToUpdate(this.positionUpdates, updated);
|
||||
updated = this._updateFixedPositions(updated);
|
||||
if (updated.length)
|
||||
this.$emit('rearrangeItems', updated.filter(v => v));
|
||||
},
|
||||
_updateFixedPositions(updated) {
|
||||
updated.forEach((item, index) => {
|
||||
if (!this.fixedPositionUpdates[index])
|
||||
this.fixedPositionUpdates[index] = item;
|
||||
else
|
||||
this.fixedPositionUpdates[index] = {...this.fixedPositionUpdates[index], ...item};
|
||||
});
|
||||
let additionalUpdates = this.createNewGrid(this.prePlacedItems);
|
||||
if (additionalUpdates.length) {
|
||||
// NOTE(chris): this should never happen but it's here for safety
|
||||
additionalUpdates.forEach((item, index) => updated[index] = item);
|
||||
return this._updateFixedPositions(updated);
|
||||
}
|
||||
return updated;
|
||||
},
|
||||
emptyTileClicked() {
|
||||
this.$emit('newItem', this.x, this.y);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
ref="container"
|
||||
class="drop-grid position-relative h-0"
|
||||
:style="gridStyle"
|
||||
@touchmove="dragOver"
|
||||
@touchend="dragCancel"
|
||||
@dragover.prevent="dragOver"
|
||||
@drop="dragEnd"
|
||||
@mousemove="updateCursor"
|
||||
@mouseleave="mouseLeave">
|
||||
<grid-item
|
||||
v-for="item in placedItems"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
@start-move="startMove"
|
||||
@start-resize="startResize"
|
||||
@end-drag="dragCancel"
|
||||
@drop-drag="dragEnd"
|
||||
class="position-absolute"
|
||||
:active="active"
|
||||
:style="{
|
||||
top: 'calc(' + item.y + ' * var(--fhc-dg-row-height))',
|
||||
left: 'calc(' + item.x + ' * var(--fhc-dg-col-width))',
|
||||
width: 'calc(' + item.w + ' * var(--fhc-dg-col-width))',
|
||||
height: 'calc(' + item.h + ' * var(--fhc-dg-row-height))'
|
||||
}">
|
||||
<template v-slot="item">
|
||||
<slot v-bind="item.data"></slot>
|
||||
</template>
|
||||
</grid-item>
|
||||
<div
|
||||
v-if="showEmptyTileHover"
|
||||
class="position-absolute d-flex justify-content-center align-items-center"
|
||||
:style="{
|
||||
cursor: 'pointer',
|
||||
top: 'calc(' + y + ' * var(--fhc-dg-row-height))',
|
||||
left: 'calc(' + x + ' * var(--fhc-dg-col-width))',
|
||||
width: 'var(--fhc-dg-col-width)',
|
||||
height: 'var(--fhc-dg-row-height)'
|
||||
}"
|
||||
@click="emptyTileClicked">
|
||||
<slot v-bind="{x,y}" name="empty-tile-hover"></slot>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
export default {
|
||||
components: {
|
||||
},
|
||||
inject: {
|
||||
},
|
||||
props: {
|
||||
item: Object,
|
||||
active: Boolean
|
||||
},
|
||||
emits: [
|
||||
"startMove",
|
||||
"startResize",
|
||||
"endDrag",
|
||||
"dropDrag"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
dragAction: '',
|
||||
dragging: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
registerDragAction(evt) {
|
||||
if (evt.target.hasAttribute('drag-action')) {
|
||||
this.dragAction = evt.target.getAttribute('drag-action');
|
||||
} else {
|
||||
let parent = evt.target.closest('[drag-action]');
|
||||
if (parent) {
|
||||
this.dragAction = parent.getAttribute('drag-action');
|
||||
} else {
|
||||
this.dragAction = '';
|
||||
}
|
||||
}
|
||||
},
|
||||
tryDragStart(evt, item) {
|
||||
let dragAction = this.dragAction || evt.target.getAttribute('drag-action');
|
||||
if (dragAction) {
|
||||
this.dragging = true;
|
||||
if (dragAction == 'move')
|
||||
return this.$emit('startMove', evt, item);
|
||||
else if (dragAction == 'resize')
|
||||
return this.$emit('startResize', evt, item);
|
||||
}
|
||||
evt.preventDefault();
|
||||
},
|
||||
touchDragEnd(evt) {
|
||||
if (!this.dragging)
|
||||
return evt.preventDefault();
|
||||
this.dragging = false;
|
||||
this.$emit('dropDrag', evt);
|
||||
},
|
||||
test(evt) {
|
||||
let dragAction = this.dragAction || evt.target.getAttribute('drag-action');
|
||||
if (dragAction) {
|
||||
this.dragging = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="drop-grid-item"
|
||||
@mousedown="registerDragAction"
|
||||
@touchstart="tryDragStart($event, item)"
|
||||
@touchend="touchDragEnd"
|
||||
@dragstart="tryDragStart($event, item)"
|
||||
@dragend="$emit('endDrag', $event)"
|
||||
:draggable="active">
|
||||
<slot v-bind="item"></slot>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// TODO(chris): Comments
|
||||
|
||||
const DIR_UP = 0;
|
||||
const DIR_LEFT = 1;
|
||||
const DIR_RIGHT = 2;
|
||||
const DIR_DOWN = 3;
|
||||
|
||||
class GridLogic {
|
||||
constructor(w) {
|
||||
if (w.w) {
|
||||
this.w = w.w;
|
||||
this.h = w.h;
|
||||
this.data = [...w.data];
|
||||
this.grid = [...w.grid];
|
||||
} else {
|
||||
this.w = w;
|
||||
this.h = 1;
|
||||
this.data = [];
|
||||
this.grid = [];
|
||||
}
|
||||
}
|
||||
isFreeSlot(x, y) {
|
||||
const i = y*this.w + x;
|
||||
return !this.grid[i] && this.grid[i] !== 0;
|
||||
}
|
||||
add(item, prefer) {
|
||||
let occupiers = this.getItemsInFrame(item.frame);
|
||||
|
||||
if (!occupiers.length) {
|
||||
item.frame.forEach(f => this.grid[f] = item.index);
|
||||
this.data[item.index] = item;
|
||||
this.h = Math.max(this.h, item.y + item.h);
|
||||
return [];
|
||||
} else {
|
||||
const intermGrid = new GridLogic(this);
|
||||
|
||||
item.frame.forEach(f => intermGrid.grid[f] = -1);
|
||||
|
||||
const possiblities = intermGrid.tryMoving(occupiers, prefer);
|
||||
if (possiblities.length) {
|
||||
const bestOption = possiblities.sort((a,b) => {
|
||||
if (a.cost === undefined)
|
||||
a.cost = a.moves.reduce((a, v) => a * v.cost, 1);
|
||||
if (b.cost === undefined)
|
||||
b.cost = b.moves.reduce((a, v) => a * v.cost, 1);
|
||||
return a.cost > b.cost;
|
||||
});
|
||||
const result = [];
|
||||
bestOption[0].moves.forEach(move => {
|
||||
const currItem = {...this.data[move.index]};
|
||||
currItem.x += move.x;
|
||||
currItem.y += move.y;
|
||||
currItem.frame = this.getItemFrame(currItem);
|
||||
this.h = Math.max(this.h, currItem.y + currItem.h);
|
||||
this.remove(currItem);
|
||||
currItem.frame.forEach(f => this.grid[f] = currItem.index);
|
||||
this.data[move.index] = currItem;
|
||||
result[move.index] = {
|
||||
index: currItem.index,
|
||||
x: currItem.x,
|
||||
y: currItem.y
|
||||
};
|
||||
});
|
||||
item.frame.forEach(f => this.grid[f] = item.index);
|
||||
this.data[item.index] = item;
|
||||
return result;
|
||||
} else {
|
||||
console.error('FATAL', "can't arrange item on grid");
|
||||
}
|
||||
}
|
||||
}
|
||||
move(item, x, y) {
|
||||
if (item.x == x && item.y == y)
|
||||
return [];
|
||||
this.remove(item);
|
||||
|
||||
let prefer = undefined;
|
||||
if (item.x == x) {
|
||||
if (y-item.y > 0)
|
||||
prefer = DIR_UP;
|
||||
else
|
||||
prefer = DIR_DOWN;
|
||||
} else if (item.y == y) {
|
||||
if (x-item.x > 0)
|
||||
prefer = DIR_LEFT;
|
||||
else
|
||||
prefer = DIR_RIGHT;
|
||||
}
|
||||
|
||||
const currItem = {...item};
|
||||
currItem.x = x;
|
||||
currItem.y = y;
|
||||
currItem.frame = this.getItemFrame(currItem);
|
||||
|
||||
const updates = this.add(currItem, prefer);
|
||||
updates[item.index] = {index: item.index, x, y};
|
||||
return updates;
|
||||
}
|
||||
resize(item, w, h) {
|
||||
if (item.w == w && item.h == h)
|
||||
return [];
|
||||
this.remove(item);
|
||||
|
||||
const currItem = {...item};
|
||||
currItem.w = w;
|
||||
currItem.h = h;
|
||||
currItem.frame = this.getItemFrame(currItem);
|
||||
|
||||
const updates = this.add(currItem);
|
||||
updates[item.index] = {index: item.index, w, h};
|
||||
return updates;
|
||||
}
|
||||
tryMoving(index, prefer) {
|
||||
if (Array.isArray(index)) {
|
||||
index.forEach(i => this.remove({index:i}));
|
||||
let possiblities = [{grid: this, moves: []}];
|
||||
index.forEach(i => {
|
||||
let newPoss = [];
|
||||
possiblities.forEach(possiblity => {
|
||||
possiblity.grid.tryMoving(i, prefer).forEach(p => {
|
||||
possiblity.moves
|
||||
p.moves = [...p.moves, ...possiblity.moves];
|
||||
newPoss.push(p)
|
||||
});
|
||||
});
|
||||
possiblities = newPoss;
|
||||
});
|
||||
return possiblities;
|
||||
}
|
||||
const directions = [DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT];
|
||||
|
||||
this.remove({index});
|
||||
|
||||
const weight = 1 + .2*(this.data[index].weight || 0);
|
||||
|
||||
return directions.reduce((result, dir) => {
|
||||
let res = this.tryMovingInDirection(dir, index, 1, (prefer === dir ? .5 : 1 + dir*.1) * weight);
|
||||
if (!res)
|
||||
return result;
|
||||
return [...result, ...res];
|
||||
}, []).filter(p => p);
|
||||
}
|
||||
tryMovingInDirection(dir, index, amount, cost) {
|
||||
const move = {index, x:0, y: 0, cost: cost};
|
||||
let targetframe;
|
||||
switch(dir) {
|
||||
case DIR_UP:
|
||||
if (this.data[index].y - amount < 0)
|
||||
return false;
|
||||
targetframe = this.data[index].frame.map(i => i-this.w*amount);
|
||||
move.y = -amount;
|
||||
break;
|
||||
case DIR_DOWN:
|
||||
if (this.data[index].y + this.data[index].h + amount > this.h)
|
||||
cost += .4;
|
||||
targetframe = this.data[index].frame.map(i => i+this.w*amount);
|
||||
move.y = amount;
|
||||
break;
|
||||
case DIR_LEFT:
|
||||
if (this.data[index].x - amount < 0)
|
||||
return false;
|
||||
targetframe = this.data[index].frame.map(i => i-amount);
|
||||
move.x = -amount;
|
||||
break;
|
||||
case DIR_RIGHT:
|
||||
if (this.data[index].x + this.data[index].w + amount > this.w)
|
||||
return false;
|
||||
targetframe = this.data[index].frame.map(i => i+amount);
|
||||
move.x = amount;
|
||||
break;
|
||||
}
|
||||
|
||||
const occupiers = this.getItemsInFrame(targetframe);
|
||||
if (occupiers.includes(-1)) {
|
||||
return this.tryMovingInDirection(dir, index, amount+1, cost);
|
||||
}
|
||||
|
||||
const intermGrid = new GridLogic(this);
|
||||
targetframe.forEach(f => intermGrid.grid[f] = -1);
|
||||
|
||||
if (!occupiers.length) {
|
||||
return [{grid: intermGrid, moves: [move]}];
|
||||
}
|
||||
const possiblities = intermGrid.tryMoving(occupiers).map(possiblity => possiblity.moves.unshift(move) && possiblity);
|
||||
return possiblities.length ? possiblities : false;
|
||||
}
|
||||
clearWeights() {
|
||||
this.data.forEach(item => item.weight = undefined);
|
||||
}
|
||||
getItemsInFrame(frame) {
|
||||
return frame.map(i => this.grid[i]).filter((v,i,a) => (v || v === 0) && a.indexOf(v) === i);
|
||||
}
|
||||
remove(item) {
|
||||
this.grid = this.grid.map(i => i != item.index ? i : undefined);
|
||||
}
|
||||
getItemFrame(item) {
|
||||
const frame = [];
|
||||
for (let i = 0; i < item.w; i++)
|
||||
for (let j = 0; j < item.h; j++)
|
||||
frame.push(i + item.x + (j + item.y) * this.w);
|
||||
return frame;
|
||||
}
|
||||
debug() {
|
||||
return this.grid;
|
||||
}
|
||||
}
|
||||
|
||||
export default GridLogic;
|
||||
@@ -369,6 +369,7 @@ GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE dashboard.tbl_dashboard_benutzer_over
|
||||
-- Name: SEQUENCE tbl_dashboard_benutzer_override_override_id_seq; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT ALL ON SEQUENCE dashboard.tbl_dashboard_benutzer_override_override_id_seq TO web;
|
||||
GRANT ALL ON SEQUENCE dashboard.tbl_dashboard_benutzer_override_override_id_seq TO vilesci;
|
||||
|
||||
|
||||
@@ -399,6 +400,7 @@ GRANT ALL ON SEQUENCE dashboard.tbl_dashboard_preset_preset_id_seq TO vilesci;
|
||||
--
|
||||
|
||||
GRANT SELECT ON TABLE dashboard.tbl_dashboard_widget TO web;
|
||||
GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE dashboard.tbl_dashboard_widget TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
@@ -406,6 +408,7 @@ GRANT SELECT ON TABLE dashboard.tbl_dashboard_widget TO web;
|
||||
--
|
||||
|
||||
GRANT SELECT ON TABLE dashboard.tbl_widget TO web;
|
||||
GRANT SELECT,INSERT,UPDATE ON TABLE dashboard.tbl_widget TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
|
||||
Reference in New Issue
Block a user