From 3da096142d44c31e180fb54c02ae9e9ff01b7dda Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 17 Jul 2019 16:52:02 +0200 Subject: [roomplanner] First prototype of composed room editor Saving/loading works, but no entry is generated for pvs.ini. Also this approach fails to meaningfully handle rooms with two removable walls where you also want to use both possible combinations of two combined rooms and a single one. --- modules-available/locations/page.inc.php | 2 +- modules-available/roomplanner/api.inc.php | 3 +- .../roomplanner/inc/composedroom.inc.php | 77 ++++++++++ .../roomplanner/inc/pvsgenerator.inc.php | 7 +- modules-available/roomplanner/page.inc.php | 169 ++++++++++++++++----- modules-available/roomplanner/style.css | 35 +++++ .../roomplanner/templates/edit-composed-room.html | 150 ++++++++++++++++++ .../roomplanner/templates/svg-plan.html | 2 +- 8 files changed, 402 insertions(+), 43 deletions(-) create mode 100644 modules-available/roomplanner/inc/composedroom.inc.php create mode 100644 modules-available/roomplanner/templates/edit-composed-room.html diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php index 5b3d7ff0..658b4b18 100644 --- a/modules-available/locations/page.inc.php +++ b/modules-available/locations/page.inc.php @@ -545,7 +545,7 @@ class Page_Locations extends Page 'locationid' => $loc['locationid'], 'locationname' => $loc['locationname'], 'list' => $rows, - 'roomplanner' => Module::get('roomplanner') !== false && Location::isLeaf($locationId), + 'roomplanner' => Module::get('roomplanner') !== false, 'parents' => Location::getLocations($loc['parentlocationid'], $locationId, true) ); diff --git a/modules-available/roomplanner/api.inc.php b/modules-available/roomplanner/api.inc.php index 055c6b2e..f964bea1 100644 --- a/modules-available/roomplanner/api.inc.php +++ b/modules-available/roomplanner/api.inc.php @@ -3,7 +3,8 @@ if (Request::any('show') === 'svg') { $ret = PvsGenerator::generateSvg(Request::any('locationid', false, 'int'), Request::any('machineuuid', false, 'string'), - Request::any('rotate', 0, 'int')); + Request::any('rotate', 0, 'int'), + Request::any('scale', 1, 'float')); if ($ret === false) { Header('HTTP/1.1 404 Not Found'); exit; diff --git a/modules-available/roomplanner/inc/composedroom.inc.php b/modules-available/roomplanner/inc/composedroom.inc.php new file mode 100644 index 00000000..11e8455e --- /dev/null +++ b/modules-available/roomplanner/inc/composedroom.inc.php @@ -0,0 +1,77 @@ + $v) { + $this->{$k} = $v; + } + } else { + if (is_array($data) && isset($data['roomplan'])) { + // From DB + $data = json_decode($data['roomplan'], true); + } elseif (is_string($data)) { + // Just JSON + $data = json_decode($data, true); + } + if (is_array($data)) { + foreach ($this as $k => $v) { + if (isset($data[$k])) { + $this->{$k} = $data[$k]; + } + } + } + } + $this->sanitize(); + } + + /** + * Make sure all member vars have the proper type + */ + private function sanitize() + { + $this->orientation = ($this->orientation === 'horizontal' ? 'horizontal' : 'vertical'); + settype($this->enabled, 'bool'); + settype($this->list, 'array'); + settype($this->controlRoom, 'int'); + foreach ($this->list as &$v) { + settype($v, 'int'); + } + $this->list = array_values($this->list); + if (!empty($this->list) && !in_array($this->controlRoom, $this->list)) { + $this->controlRoom = $this->list[0]; + } + } + + /** + * @return false|string JSON + */ + public function serialize() + { + $this->sanitize(); + return json_encode($this); + } + +} diff --git a/modules-available/roomplanner/inc/pvsgenerator.inc.php b/modules-available/roomplanner/inc/pvsgenerator.inc.php index 2a4e2972..cfb38fd2 100644 --- a/modules-available/roomplanner/inc/pvsgenerator.inc.php +++ b/modules-available/roomplanner/inc/pvsgenerator.inc.php @@ -156,7 +156,7 @@ class PvsGenerator * @param int $rotate rotate plan (0-3 for N E S W up, -1 for "auto" if highlightUuid is given) * @return string SVG */ - public static function generateSvg($locationId = false, $highlightUuid = false, $rotate = 0) + public static function generateSvg($locationId = false, $highlightUuid = false, $rotate = 0, $scale = 1) { if ($locationId === false) { $locationId = Database::queryFirst('SELECT fixedlocationid FROM machine @@ -215,8 +215,9 @@ class PvsGenerator self::swap($sizeX, $sizeY); } return Render::parse('svg-plan', [ - 'width' => $sizeX, - 'height' => $sizeY, + 'scale' => $scale, + 'width' => $sizeX * $scale, + 'height' => $sizeY * $scale, 'centerX' => $centerX, 'centerY' => $centerY, 'rotate' => $rotate * 90, diff --git a/modules-available/roomplanner/page.inc.php b/modules-available/roomplanner/page.inc.php index 8b75499b..d1543a9e 100644 --- a/modules-available/roomplanner/page.inc.php +++ b/modules-available/roomplanner/page.inc.php @@ -18,11 +18,20 @@ class Page_Roomplanner extends Page */ private $action = false; + /** + * @var bool is this a leaf node, with a real room plan, or something in between with a composed plan + */ + private $isLeaf; + private function loadRequestedLocation() { $this->locationid = Request::get('locationid', false, 'integer'); if ($this->locationid !== false) { - $this->location = Location::get($this->locationid); + $locs = Location::getLocationsAssoc(); + if (isset($locs[$this->locationid])) { + $this->location = $locs[$this->locationid]; + $this->isLeaf = empty($this->location['children']); + } } } @@ -58,48 +67,93 @@ class Page_Roomplanner extends Page if ($this->action === 'show') { /* do nothing */ Dashboard::disable(); - $config = Database::queryFirst('SELECT roomplan, managerip, tutoruuid FROM location_roomplan WHERE locationid = :locationid', ['locationid' => $this->locationid]); - $runmode = RunMode::getForMode(Page::getModule(), $this->locationid, true); - if (empty($runmode)) { - $config['dedicatedmgr'] = false; - } else { - $runmode = array_pop($runmode); - $config['managerip'] = $runmode['clientip']; - $config['manageruuid'] = $runmode['machineuuid']; - $data = json_decode($runmode['modedata'], true); - $config['dedicatedmgr'] = (isset($data['dedicatedmgr']) && $data['dedicatedmgr']); - } - if ($config !== false) { - $managerIp = $config['managerip']; - $dediMgr = $config['dedicatedmgr'] ? 'checked' : ''; + if ($this->isLeaf) { + $this->showLeafEditor(); } else { - $dediMgr = $managerIp = ''; + $this->showComposedEditor(); } - $furniture = $this->getFurniture($config); - $subnetMachines = $this->getPotentialMachines(); - $machinesOnPlan = $this->getMachinesOnPlan($config['tutoruuid']); - $roomConfig = array_merge($furniture, $machinesOnPlan); - $canEdit = User::hasPermission('edit', $this->locationid); - $params = [ - 'location' => $this->location, - 'managerip' => $managerIp, - 'dediMgrChecked' => $dediMgr, - 'subnetMachines' => json_encode($subnetMachines), - 'locationid' => $this->locationid, - 'roomConfiguration' => json_encode($roomConfig), - 'edit_disabled' => $canEdit ? '' : 'disabled', - 'statistics_disabled' => Module::get('statistics') !== false && User::hasPermission('.statistics.machine.view-details') ? '' : 'disabled', - ]; - Render::addTemplate('header', $params); - if ($canEdit) { - Render::addTemplate('item-selector', $params); - } - Render::addTemplate('main-roomplan', $params); - Render::addTemplate('footer', $params); } else { Message::addError('main.invalid-action', $this->action); } + } + + private function showLeafEditor() + { + $config = Database::queryFirst('SELECT roomplan, managerip, tutoruuid FROM location_roomplan WHERE locationid = :locationid', ['locationid' => $this->locationid]); + $runmode = RunMode::getForMode(Page::getModule(), $this->locationid, true); + if (empty($runmode)) { + $config['dedicatedmgr'] = false; + } else { + $runmode = array_pop($runmode); + $config['managerip'] = $runmode['clientip']; + $config['manageruuid'] = $runmode['machineuuid']; + $data = json_decode($runmode['modedata'], true); + $config['dedicatedmgr'] = (isset($data['dedicatedmgr']) && $data['dedicatedmgr']); + } + if ($config !== false) { + $managerIp = $config['managerip']; + $dediMgr = $config['dedicatedmgr'] ? 'checked' : ''; + } else { + $dediMgr = $managerIp = ''; + } + $furniture = $this->getFurniture($config); + $subnetMachines = $this->getPotentialMachines(); + $machinesOnPlan = $this->getMachinesOnPlan($config['tutoruuid']); + $roomConfig = array_merge($furniture, $machinesOnPlan); + $canEdit = User::hasPermission('edit', $this->locationid); + $params = [ + 'location' => $this->location, + 'managerip' => $managerIp, + 'dediMgrChecked' => $dediMgr, + 'subnetMachines' => json_encode($subnetMachines), + 'locationid' => $this->locationid, + 'roomConfiguration' => json_encode($roomConfig), + 'edit_disabled' => $canEdit ? '' : 'disabled', + 'statistics_disabled' => Module::get('statistics') !== false && User::hasPermission('.statistics.machine.view-details') ? '' : 'disabled', + ]; + Render::addTemplate('header', $params); + if ($canEdit) { + Render::addTemplate('item-selector', $params); + } + Render::addTemplate('main-roomplan', $params); + Render::addTemplate('footer', $params); + } + private function showComposedEditor() + { + // Load settings + $row = Database::queryFirst("SELECT roomplan FROM location_roomplan WHERE locationid = :lid", [ + 'lid' => $this->locationid, + ]); + $room = new ComposedRoom($row); + $params = [ + 'location' => $this->location, + 'locations' => [], + $room->orientation . '_checked' => 'checked', + ]; + if ($room->enabled) { + $params['enabled_checked'] = 'checked'; + } + $inverseList = array_flip($room->list); + $sortList = []; + // Load locations + $locs = Location::getLocationsAssoc(); + foreach ($this->location['children'] as $loc) { + if (isset($locs[$loc])) { + $data = $locs[$loc]; + if (isset($inverseList[$loc])) { + $sortList[] = $inverseList[$loc]; + } else { + $sortList[] = 1000 + $loc; + } + if ($loc === $room->controlRoom) { + $data['checked'] = 'checked'; + } + $params['locations'][] = $data; + } + } + array_multisort($sortList, SORT_ASC | SORT_NUMERIC, $params['locations']); + Render::addTemplate('edit-composed-room', $params); } protected function doAjax() @@ -160,6 +214,25 @@ class Page_Roomplanner extends Page private function handleSaveRequest($isAjax) { User::assertPermission('edit', $this->locationid); + $leaf = (bool)Request::post('isleaf', 1, 'int'); + if ($leaf !== $this->isLeaf) { + if ($isAjax) { + die('Leaf mode mismatch. Did you restructure locations while editing this room?'); + } else { + Message::addError('leaf-mode-mismatch'); + Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show"); + } + return; + } + if ($this->isLeaf) { + $this->saveLeafRoom($isAjax); + } else { + $this->saveComposedRoom($isAjax); + } + } + + private function saveLeafRoom($isAjax) + { $machinesOnPlan = $this->getMachinesOnPlan('invalid'); $config = Request::post('serializedRoom', null, 'string'); $config = json_decode($config, true); @@ -189,6 +262,28 @@ class Page_Roomplanner extends Page $this->saveComputerConfig($config['computers'], $machinesOnPlan); } + private function saveComposedRoom($isAjax) + { + $room = new ComposedRoom(null); + $room->orientation = Request::post('orientation', 'horizontal', 'string'); + $room->enabled = (bool)Request::post('enabled', 0, 'int'); + $room->controlRoom = Request::post('controlroom', 0, 'int'); + $vals = Request::post('sort', [], 'array'); + asort($vals, SORT_ASC | SORT_NUMERIC); + $room->list = array_keys($vals); + $res = Database::exec('INSERT INTO location_roomplan (locationid, roomplan) + VALUES (:lid, :plan) ON DUPLICATE KEY UPDATE roomplan = VALUES(roomplan)', + ['lid' => $this->locationid, 'plan' => $room->serialize()]); + if (!$res) { + if ($isAjax) { + die('Error writing config to DB'); + } else { + Message::addError('db-error'); + Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show"); + } + } + } + private function sanitizeNumber(&$number, $lower, $upper) { if (!is_numeric($number) || $number < $lower) { diff --git a/modules-available/roomplanner/style.css b/modules-available/roomplanner/style.css index f1dd994a..8f516465 100644 --- a/modules-available/roomplanner/style.css +++ b/modules-available/roomplanner/style.css @@ -23,6 +23,41 @@ body { /* end full screen changes */ +/* sort mode for composed rooms */ + +#roomsort div.img { + border: none; +} + +div.box1 { + padding: 2px; +} + +div.horizontal div.box1 { + float: left; + display: inline-table; +} + +div.horizontal div.name { + width: 5px; + overflow: visible; + height: 1em; +} + +div.vertical div.box2 { + float: left; +} + +div.vertical div.name { + float: right; +} + +div.vertical div.img { + float: left; +} + +/* end sort mode for composed rooms */ + #drawpanel { position:relative;} diff --git a/modules-available/roomplanner/templates/edit-composed-room.html b/modules-available/roomplanner/templates/edit-composed-room.html new file mode 100644 index 00000000..64a02d61 --- /dev/null +++ b/modules-available/roomplanner/templates/edit-composed-room.html @@ -0,0 +1,150 @@ +

{{lang_editComposedRoom}}

+

{{location.locationname}}

+ +
+ + + +
+
+
+ + +
+
+
+
+

{{lang_composedLayout}}

+
+ + +
+
+ + +
+

+
+ {{#locations}} +
+
+
{{locationname}}
+
+ + +
+
+
+
+
+ {{/locations}} +
+
+
+

{{lang_controllingRoom}}

+

{{lang_controlRoomDesc}}

+ {{#locations}} +
+
+ + +
+
+ {{/locations}} +
+
+ + +
+
+ + + +
+
+
+ + \ No newline at end of file diff --git a/modules-available/roomplanner/templates/svg-plan.html b/modules-available/roomplanner/templates/svg-plan.html index 072efbff..16899e5c 100644 --- a/modules-available/roomplanner/templates/svg-plan.html +++ b/modules-available/roomplanner/templates/svg-plan.html @@ -33,7 +33,7 @@ - + -- cgit v1.2.3-55-g7522