summaryrefslogtreecommitdiffstats
path: root/modules-available
diff options
context:
space:
mode:
authorSimon Rettberg2019-07-17 16:52:02 +0200
committerSimon Rettberg2019-07-17 16:52:02 +0200
commit3da096142d44c31e180fb54c02ae9e9ff01b7dda (patch)
treec715a36c2fbbcd8e17bf7f80395fd4677416ec95 /modules-available
parent[roomplanner] Use .cachedScript() from slx-fixes (diff)
downloadslx-admin-3da096142d44c31e180fb54c02ae9e9ff01b7dda.tar.gz
slx-admin-3da096142d44c31e180fb54c02ae9e9ff01b7dda.tar.xz
slx-admin-3da096142d44c31e180fb54c02ae9e9ff01b7dda.zip
[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.
Diffstat (limited to 'modules-available')
-rw-r--r--modules-available/locations/page.inc.php2
-rw-r--r--modules-available/roomplanner/api.inc.php3
-rw-r--r--modules-available/roomplanner/inc/composedroom.inc.php77
-rw-r--r--modules-available/roomplanner/inc/pvsgenerator.inc.php7
-rw-r--r--modules-available/roomplanner/page.inc.php169
-rw-r--r--modules-available/roomplanner/style.css35
-rw-r--r--modules-available/roomplanner/templates/edit-composed-room.html150
-rw-r--r--modules-available/roomplanner/templates/svg-plan.html2
8 files changed, 402 insertions, 43 deletions
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 @@
+<?php
+
+class ComposedRoom
+{
+ /**
+ * @var string How to compose contained rooms. Value is either horizontal or vertical.
+ */
+ public $orientation = 'horizontal';
+
+ /**
+ * @var int[] Order in which contained rooms are composed. List of locationid.
+ */
+ public $list;
+
+ /**
+ * @var bool Whether composed room is active, ie. visible in PVS.
+ */
+ public $enabled;
+
+ /**
+ * @var int locationid of contained room that is the controlling room;
+ */
+ public $controlRoom;
+
+ public function __construct($data)
+ {
+ if ($data instanceof ComposedRoom) {
+ foreach ($data as $k => $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 @@
+<h1>{{lang_editComposedRoom}}</h1>
+<h2>{{location.locationname}}</h2>
+
+<form id="main-form" method="post" action="?do=roomplanner&amp;locationid={{location.locationid}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="isleaf" value="0">
+ <input type="hidden" name="action" value="save">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <div class="checkbox">
+ <input id="check-enable" type="checkbox" name="enabled" value="1" {{enabled_checked}}>
+ <label for="check-enable">{{lang_exposeAsComposedRoom}}</label>
+ </div>
+ </div>
+ <div class="panel-body">
+ <div id="main-controls">
+ <h4>{{lang_composedLayout}}</h4>
+ <div class="radio radio-inline">
+ <input id="type-horz" type="radio" name="orientation" value="horizontal" {{horizontal_checked}}>
+ <label for="type-horz">{{lang_horizontal}}</label>
+ </div>
+ <div class="radio radio-inline">
+ <input id="type-vert" type="radio" name="orientation" value="vertical" {{vertical_checked}}>
+ <label for="type-vert">{{lang_vertical}}</label>
+ </div>
+ <br><br>
+ <div id="roomsort">
+ {{#locations}}
+ <div class="box1">
+ <div class="box2">
+ <div class="name text-nowrap small">{{locationname}}</div>
+ <div class="img">
+ <input type="hidden" class="sort-val" name="sort[{{locationid}}]">
+ <img src="api.php?do=roomplanner&amp;show=svg&amp;locationid={{locationid}}&amp;scale=2.2">
+ </div>
+ <div class="clearfix"></div>
+ </div>
+ <div class="clearfix"></div>
+ </div>
+ {{/locations}}
+ </div>
+ <div class="clearfix"></div>
+ <br>
+ <h4>{{lang_controllingRoom}}</h4>
+ <p>{{lang_controlRoomDesc}}</p>
+ {{#locations}}
+ <div>
+ <div class="radio">
+ <input id="control-{{locationid}}" type="radio" name="controlroom"
+ value="{{locationid}}" {{checked}}>
+ <label for="control-{{locationid}}">{{locationname}}</label>
+ </div>
+ </div>
+ {{/locations}}
+ </div>
+ <div class="buttonbar pull-right">
+ <button type="button" class="btn btn-default" id="btn-cancel">
+ {{lang_cancel}}
+ </button>
+ <button id="btn-save" type="submit" class="btn btn-primary">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ <div class="clearfix"></div>
+ <div class="alert alert-danger" style="display:none" id="error-msg"></div>
+ <div class="alert alert-success" style="display:none" id="success-msg">{{lang_planSuccessfullySaved}}</div>
+ <div class="alert alert-info" style="display:none" id="saving-msg">{{lang_planBeingSaved}}</div>
+ </div>
+ </div>
+</form>
+
+<script>
+ document.addEventListener('DOMContentLoaded', function () {
+
+ var reassignSortValues = function () {
+ var startValue = 1;
+ $('.sort-val').each(function (index, element) {
+ element.value = startValue * 10;
+ startValue++;
+ });
+ };
+
+ var $rs = $('#roomsort');
+ var $mc = $('#main-controls');
+
+ $rs.disableSelection().sortable({
+ opacity: 0.8,
+ start: function (evt, ui) {
+ ui.placeholder.css("visibility", "visible");
+ ui.placeholder.css("opacity", "0.352");
+ ui.placeholder.css("background-color", "#ddd");
+ },
+ stop: reassignSortValues
+ });
+
+ var setLayout = function () {
+ $rs.removeClass('horizontal vertical').addClass($('input[name=orientation]:checked').val());
+ };
+ $('input[name=orientation]').change(setLayout);
+
+ $('#btn-cancel').click(function () {
+ window.close();
+ });
+
+ var $ce = $('#check-enable');
+
+ var checkEnable = function () {
+ if ($ce.is(':checked')) {
+ $mc.show();
+ } else {
+ $mc.hide();
+ }
+ };
+
+ $ce.change(checkEnable);
+
+ var $mf = $('#main-form');
+ var $sb = $('#btn-save');
+ var success = false;
+ $sb.click(function(e) {
+ $sb.prop('disabled', true);
+ $('#error-msg').hide();
+ $('#success-msg').hide();
+ $('#saving-msg').show();
+ var str = $mf.serialize();
+ $.post($mf.attr('action'), str).done(function (data) {
+ if (data.indexOf('SUCCESS') !== -1) {
+ window.close();
+ // If window.close() failed, we give some feedback and remember the state as saved
+ $('#success-msg').show();
+ success = true;
+ return;
+ }
+ $('#error-msg').text('Error: ' + data).show();
+ }).fail(function (jq, textStatus, errorThrown) {
+ $('#error-msg').text('AJAX save call failed: ' + textStatus + ' (' + errorThrown + ')').show();
+ }).always(function() {
+ $sb.prop('disabled', success);
+ $('#saving-msg').hide();
+ });
+ e.preventDefault();
+ });
+
+ setLayout();
+ reassignSortValues();
+ checkEnable();
+
+ });
+</script> \ 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 @@
<stop offset="100%" stop-color="#074" />
</radialGradient>
</defs>
- <g transform="rotate({{rotate}} {{centerX}} {{centerY}}) translate({{shiftX}} {{shiftY}})">
+ <g transform="scale({{scale}}) rotate({{rotate}} {{centerX}} {{centerY}}) translate({{shiftX}} {{shiftY}})">
<line x1="{{line.x1}}" y1="{{line.y1}}"
x2="{{line.x2}}" y2="{{line.y2}}"
style="stroke:#555;stroke-width:.2;opacity:.5" />