mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
Restore column point counts to workboards
Summary: Ref T4427. Test Plan: - Dragged a 17 XP task from "Hunting" to "Slain". - Saw 17 XP move. - Level up! Reviewers: chad Reviewed By: chad Maniphest Tasks: T4427 Differential Revision: https://secure.phabricator.com/D15237
This commit is contained in:
parent
888ea4e4fe
commit
1fb76655df
7 changed files with 212 additions and 82 deletions
|
@ -221,6 +221,13 @@ final class ManiphestTask extends ManiphestDAO
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWorkboardProperties() {
|
||||||
|
return array(
|
||||||
|
'status' => $this->getStatus(),
|
||||||
|
'points' => (double)$this->getPoints(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
/* -( PhabricatorSubscribableInterface )----------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -318,14 +318,17 @@ final class PhabricatorProjectBoardViewController
|
||||||
$column_menu = $this->buildColumnMenu($project, $column);
|
$column_menu = $this->buildColumnMenu($project, $column);
|
||||||
$panel->addHeaderAction($column_menu);
|
$panel->addHeaderAction($column_menu);
|
||||||
|
|
||||||
$tag_id = celerity_generate_unique_node_id();
|
|
||||||
$tag_content_id = celerity_generate_unique_node_id();
|
|
||||||
|
|
||||||
$count_tag = id(new PHUITagView())
|
$count_tag = id(new PHUITagView())
|
||||||
->setType(PHUITagView::TYPE_SHADE)
|
->setType(PHUITagView::TYPE_SHADE)
|
||||||
->setShade(PHUITagView::COLOR_BLUE)
|
->setShade(PHUITagView::COLOR_BLUE)
|
||||||
->setID($tag_id)
|
->addSigil('column-points')
|
||||||
->setName(phutil_tag('span', array('id' => $tag_content_id), '-'))
|
->setName(
|
||||||
|
javelin_tag(
|
||||||
|
'span',
|
||||||
|
array(
|
||||||
|
'sigil' => 'column-points-content',
|
||||||
|
),
|
||||||
|
pht('-')))
|
||||||
->setStyle('display: none');
|
->setStyle('display: none');
|
||||||
|
|
||||||
$panel->setHeaderTag($count_tag);
|
$panel->setHeaderTag($count_tag);
|
||||||
|
@ -339,8 +342,6 @@ final class PhabricatorProjectBoardViewController
|
||||||
->setMetadata(
|
->setMetadata(
|
||||||
array(
|
array(
|
||||||
'columnPHID' => $column->getPHID(),
|
'columnPHID' => $column->getPHID(),
|
||||||
'countTagID' => $tag_id,
|
|
||||||
'countTagContentID' => $tag_content_id,
|
|
||||||
'pointLimit' => $column->getPointLimit(),
|
'pointLimit' => $column->getPointLimit(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -359,17 +360,22 @@ final class PhabricatorProjectBoardViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
$behavior_config = array(
|
$behavior_config = array(
|
||||||
'boardID' => $board_id,
|
|
||||||
'projectPHID' => $project->getPHID(),
|
|
||||||
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
||||||
'createURI' => $this->getCreateURI(),
|
'createURI' => $this->getCreateURI(),
|
||||||
'uploadURI' => '/file/dropupload/',
|
'uploadURI' => '/file/dropupload/',
|
||||||
'coverURI' => $this->getApplicationURI('cover/'),
|
'coverURI' => $this->getApplicationURI('cover/'),
|
||||||
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
|
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
|
||||||
|
'pointsEnabled' => ManiphestTaskPoints::getIsEnabled(),
|
||||||
|
|
||||||
|
'boardPHID' => $project->getPHID(),
|
||||||
'order' => $this->sortKey,
|
'order' => $this->sortKey,
|
||||||
'templateMap' => $templates,
|
'templateMap' => $templates,
|
||||||
'columnMaps' => $column_maps,
|
'columnMaps' => $column_maps,
|
||||||
'orderMaps' => mpull($all_tasks, 'getWorkboardOrderVectors'),
|
'orderMaps' => mpull($all_tasks, 'getWorkboardOrderVectors'),
|
||||||
|
'propertyMaps' => mpull($all_tasks, 'getWorkboardProperties'),
|
||||||
|
|
||||||
|
'boardID' => $board_id,
|
||||||
|
'projectPHID' => $project->getPHID(),
|
||||||
);
|
);
|
||||||
$this->initBehavior('project-boards', $behavior_config);
|
$this->initBehavior('project-boards', $behavior_config);
|
||||||
|
|
||||||
|
|
|
@ -80,25 +80,6 @@ final class PhabricatorBoardResponseEngine extends Phobject {
|
||||||
$order_maps[$visible->getPHID()] = $visible->getWorkboardOrderVectors();
|
$order_maps[$visible->getPHID()] = $visible->getWorkboardOrderVectors();
|
||||||
}
|
}
|
||||||
|
|
||||||
$template = $this->buildTemplate();
|
|
||||||
|
|
||||||
$payload = array(
|
|
||||||
'objectPHID' => $object_phid,
|
|
||||||
'cardHTML' => $template,
|
|
||||||
'columnMaps' => $natural,
|
|
||||||
'orderMaps' => $order_maps,
|
|
||||||
);
|
|
||||||
|
|
||||||
return id(new AphrontAjaxResponse())
|
|
||||||
->setContent($payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildTemplate() {
|
|
||||||
$viewer = $this->getViewer();
|
|
||||||
$object_phid = $this->getObjectPHID();
|
|
||||||
|
|
||||||
$excluded_phids = $this->loadExcludedProjectPHIDs();
|
|
||||||
|
|
||||||
$object = id(new ManiphestTaskQuery())
|
$object = id(new ManiphestTaskQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withPHIDs(array($object_phid))
|
->withPHIDs(array($object_phid))
|
||||||
|
@ -108,6 +89,28 @@ final class PhabricatorBoardResponseEngine extends Phobject {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$template = $this->buildTemplate($object);
|
||||||
|
|
||||||
|
$payload = array(
|
||||||
|
'objectPHID' => $object_phid,
|
||||||
|
'cardHTML' => $template,
|
||||||
|
'columnMaps' => $natural,
|
||||||
|
'orderMaps' => $order_maps,
|
||||||
|
'propertyMaps' => array(
|
||||||
|
$object_phid => $object->getWorkboardProperties(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return id(new AphrontAjaxResponse())
|
||||||
|
->setContent($payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildTemplate($object) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$object_phid = $this->getObjectPHID();
|
||||||
|
|
||||||
|
$excluded_phids = $this->loadExcludedProjectPHIDs();
|
||||||
|
|
||||||
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setObjects(array($object))
|
->setObjects(array($object))
|
||||||
|
|
|
@ -19,11 +19,13 @@ JX.install('WorkboardBoard', {
|
||||||
|
|
||||||
this._templates = {};
|
this._templates = {};
|
||||||
this._orderMaps = {};
|
this._orderMaps = {};
|
||||||
|
this._propertiesMap = {};
|
||||||
this._buildColumns();
|
this._buildColumns();
|
||||||
},
|
},
|
||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
order: null,
|
order: null,
|
||||||
|
pointsEnabled: false
|
||||||
},
|
},
|
||||||
|
|
||||||
members: {
|
members: {
|
||||||
|
@ -33,6 +35,7 @@ JX.install('WorkboardBoard', {
|
||||||
_columns: null,
|
_columns: null,
|
||||||
_templates: null,
|
_templates: null,
|
||||||
_orderMaps: null,
|
_orderMaps: null,
|
||||||
|
_propertiesMap: null,
|
||||||
|
|
||||||
getRoot: function() {
|
getRoot: function() {
|
||||||
return this._root;
|
return this._root;
|
||||||
|
@ -55,6 +58,15 @@ JX.install('WorkboardBoard', {
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setObjectProperties: function(phid, properties) {
|
||||||
|
this._propertiesMap[phid] = properties;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
getObjectProperties: function(phid) {
|
||||||
|
return this._propertiesMap[phid];
|
||||||
|
},
|
||||||
|
|
||||||
getCardTemplate: function(phid) {
|
getCardTemplate: function(phid) {
|
||||||
return this._templates[phid];
|
return this._templates[phid];
|
||||||
},
|
},
|
||||||
|
@ -174,12 +186,18 @@ JX.install('WorkboardBoard', {
|
||||||
var card = src_column.removeCard(response.objectPHID);
|
var card = src_column.removeCard(response.objectPHID);
|
||||||
dst_column.addCard(card, after_phid);
|
dst_column.addCard(card, after_phid);
|
||||||
|
|
||||||
|
src_column.markForRedraw();
|
||||||
|
dst_column.markForRedraw();
|
||||||
|
|
||||||
this.updateCard(response);
|
this.updateCard(response);
|
||||||
|
|
||||||
list.unlock();
|
list.unlock();
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCard: function(response) {
|
updateCard: function(response, options) {
|
||||||
|
options = options || {};
|
||||||
|
options.dirtyColumns = options.dirtyColumns || {};
|
||||||
|
|
||||||
var columns = this.getColumns();
|
var columns = this.getColumns();
|
||||||
|
|
||||||
var phid = response.objectPHID;
|
var phid = response.objectPHID;
|
||||||
|
@ -202,8 +220,15 @@ JX.install('WorkboardBoard', {
|
||||||
this.getColumn(natural_phid).setNaturalOrder(column_maps[natural_phid]);
|
this.getColumn(natural_phid).setNaturalOrder(column_maps[natural_phid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var property_maps = response.propertyMaps;
|
||||||
|
for (var property_phid in property_maps) {
|
||||||
|
this.setObjectProperties(property_phid, property_maps[property_phid]);
|
||||||
|
}
|
||||||
|
|
||||||
for (var column_phid in columns) {
|
for (var column_phid in columns) {
|
||||||
var cards = columns[column_phid].getCards();
|
var column = columns[column_phid];
|
||||||
|
|
||||||
|
var cards = column.getCards();
|
||||||
for (var object_phid in cards) {
|
for (var object_phid in cards) {
|
||||||
if (object_phid !== phid) {
|
if (object_phid !== phid) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -211,8 +236,20 @@ JX.install('WorkboardBoard', {
|
||||||
|
|
||||||
var card = cards[object_phid];
|
var card = cards[object_phid];
|
||||||
card.redraw();
|
card.redraw();
|
||||||
|
|
||||||
|
column.markForRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._redrawColumns();
|
||||||
|
},
|
||||||
|
|
||||||
|
_redrawColumns: function() {
|
||||||
|
var columns = this.getColumns();
|
||||||
|
for (var k in columns) {
|
||||||
|
if (columns[k].isMarkedForRedraw()) {
|
||||||
|
columns[k].redraw();
|
||||||
}
|
}
|
||||||
columns[column_phid].redraw();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,18 @@ JX.install('WorkboardCard', {
|
||||||
this._column = column;
|
this._column = column;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getProperties: function() {
|
||||||
|
return this.getColumn().getBoard().getObjectProperties(this.getPHID());
|
||||||
|
},
|
||||||
|
|
||||||
|
getPoints: function() {
|
||||||
|
return this.getProperties().points;
|
||||||
|
},
|
||||||
|
|
||||||
|
getStatus: function() {
|
||||||
|
return this.getProperties().status;
|
||||||
|
},
|
||||||
|
|
||||||
getNode: function() {
|
getNode: function() {
|
||||||
if (!this._root) {
|
if (!this._root) {
|
||||||
var phid = this.getPHID();
|
var phid = this.getPHID();
|
||||||
|
|
|
@ -12,6 +12,14 @@ JX.install('WorkboardColumn', {
|
||||||
this._phid = phid;
|
this._phid = phid;
|
||||||
this._root = root;
|
this._root = root;
|
||||||
|
|
||||||
|
this._panel = JX.DOM.findAbove(root, 'div', 'workpanel');
|
||||||
|
this._pointsNode = JX.DOM.find(this._panel, 'span', 'column-points');
|
||||||
|
|
||||||
|
this._pointsContentNode = JX.DOM.find(
|
||||||
|
this._panel,
|
||||||
|
'span',
|
||||||
|
'column-points-content');
|
||||||
|
|
||||||
this._cards = {};
|
this._cards = {};
|
||||||
this._naturalOrder = [];
|
this._naturalOrder = [];
|
||||||
},
|
},
|
||||||
|
@ -22,6 +30,10 @@ JX.install('WorkboardColumn', {
|
||||||
_board: null,
|
_board: null,
|
||||||
_cards: null,
|
_cards: null,
|
||||||
_naturalOrder: null,
|
_naturalOrder: null,
|
||||||
|
_panel: null,
|
||||||
|
_pointsNode: null,
|
||||||
|
_pointsContentNode: null,
|
||||||
|
_dirty: true,
|
||||||
|
|
||||||
getPHID: function() {
|
getPHID: function() {
|
||||||
return this._phid;
|
return this._phid;
|
||||||
|
@ -48,6 +60,18 @@ JX.install('WorkboardColumn', {
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getPointsNode: function() {
|
||||||
|
return this._pointsNode;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPointsContentNode: function() {
|
||||||
|
return this._pointsContentNode;
|
||||||
|
},
|
||||||
|
|
||||||
|
getWorkpanelNode: function() {
|
||||||
|
return this._panel;
|
||||||
|
},
|
||||||
|
|
||||||
newCard: function(phid) {
|
newCard: function(phid) {
|
||||||
var card = new JX.WorkboardCard(this, phid);
|
var card = new JX.WorkboardCard(this, phid);
|
||||||
|
|
||||||
|
@ -112,8 +136,21 @@ JX.install('WorkboardColumn', {
|
||||||
return JX.keys(this.getCards());
|
return JX.keys(this.getCards());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getPointLimit: function() {
|
||||||
|
return JX.Stratcom.getData(this.getRoot()).pointLimit;
|
||||||
|
},
|
||||||
|
|
||||||
|
markForRedraw: function() {
|
||||||
|
this._dirty = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
isMarkedForRedraw: function() {
|
||||||
|
return this._dirty;
|
||||||
|
},
|
||||||
|
|
||||||
redraw: function() {
|
redraw: function() {
|
||||||
var order = this.getBoard().getOrder();
|
var board = this.getBoard();
|
||||||
|
var order = board.getOrder();
|
||||||
|
|
||||||
var list;
|
var list;
|
||||||
if (order == 'natural') {
|
if (order == 'natural') {
|
||||||
|
@ -124,11 +161,18 @@ JX.install('WorkboardColumn', {
|
||||||
|
|
||||||
var content = [];
|
var content = [];
|
||||||
for (var ii = 0; ii < list.length; ii++) {
|
for (var ii = 0; ii < list.length; ii++) {
|
||||||
var node = list[ii].getNode();
|
var card = list[ii];
|
||||||
|
|
||||||
|
var node = card.getNode();
|
||||||
content.push(node);
|
content.push(node);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JX.DOM.setContent(this.getRoot(), content);
|
JX.DOM.setContent(this.getRoot(), content);
|
||||||
|
|
||||||
|
this._redrawFrame();
|
||||||
|
|
||||||
|
this._dirty = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
_getCardsSortedNaturally: function() {
|
_getCardsSortedNaturally: function() {
|
||||||
|
@ -170,6 +214,69 @@ JX.install('WorkboardColumn', {
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
_redrawFrame: function() {
|
||||||
|
var cards = this.getCards();
|
||||||
|
var board = this.getBoard();
|
||||||
|
|
||||||
|
var points = {};
|
||||||
|
for (var phid in cards) {
|
||||||
|
var card = cards[phid];
|
||||||
|
|
||||||
|
var card_points;
|
||||||
|
if (board.getPointsEnabled()) {
|
||||||
|
card_points = card.getPoints();
|
||||||
|
} else {
|
||||||
|
card_points = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card_points !== null) {
|
||||||
|
var status = card.getStatus();
|
||||||
|
if (!points[status]) {
|
||||||
|
points[status] = 0;
|
||||||
|
}
|
||||||
|
points[status] += card_points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var total_points = 0;
|
||||||
|
for (var k in points) {
|
||||||
|
total_points += points[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
var limit = this.getPointLimit();
|
||||||
|
|
||||||
|
var display_value;
|
||||||
|
if (limit !== null && limit !== 0) {
|
||||||
|
display_value = total_points + ' / ' + limit;
|
||||||
|
} else {
|
||||||
|
display_value = total_points;
|
||||||
|
}
|
||||||
|
|
||||||
|
var over_limit = ((limit !== null) && (total_points > limit));
|
||||||
|
|
||||||
|
var content_node = this.getPointsContentNode();
|
||||||
|
var points_node = this.getPointsNode();
|
||||||
|
|
||||||
|
JX.DOM.setContent(content_node, display_value);
|
||||||
|
|
||||||
|
var is_empty = !this.getCardPHIDs().length;
|
||||||
|
var panel = JX.DOM.findAbove(this.getRoot(), 'div', 'workpanel');
|
||||||
|
JX.DOM.alterClass(panel, 'project-panel-empty', is_empty);
|
||||||
|
JX.DOM.alterClass(panel, 'project-panel-over-limit', over_limit);
|
||||||
|
|
||||||
|
var color_map = {
|
||||||
|
'phui-tag-shade-disabled': (total_points === 0),
|
||||||
|
'phui-tag-shade-blue': (total_points > 0 && !over_limit),
|
||||||
|
'phui-tag-shade-red': (over_limit)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var c in color_map) {
|
||||||
|
JX.DOM.alterClass(points_node, c, !!color_map[c]);
|
||||||
|
}
|
||||||
|
|
||||||
|
JX.DOM.show(points_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,54 +11,6 @@
|
||||||
|
|
||||||
JX.behavior('project-boards', function(config, statics) {
|
JX.behavior('project-boards', function(config, statics) {
|
||||||
|
|
||||||
|
|
||||||
function onupdate(col) {
|
|
||||||
var data = JX.Stratcom.getData(col);
|
|
||||||
var cards = finditems(col);
|
|
||||||
|
|
||||||
// Update the count of tasks in the column header.
|
|
||||||
if (!data.countTagNode) {
|
|
||||||
data.countTagNode = JX.$(data.countTagID);
|
|
||||||
JX.DOM.show(data.countTagNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
var sum = 0;
|
|
||||||
for (var ii = 0; ii < cards.length; ii++) {
|
|
||||||
// TODO: Allow this to be computed in some more clever way.
|
|
||||||
sum += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is a little bit hacky, but we don't have a PHUIX version of
|
|
||||||
// this element yet.
|
|
||||||
|
|
||||||
var over_limit = (data.pointLimit && (sum > data.pointLimit));
|
|
||||||
|
|
||||||
var display_value = sum;
|
|
||||||
if (data.pointLimit) {
|
|
||||||
display_value = sum + ' / ' + data.pointLimit;
|
|
||||||
}
|
|
||||||
JX.DOM.setContent(JX.$(data.countTagContentID), display_value);
|
|
||||||
|
|
||||||
|
|
||||||
var panel_map = {
|
|
||||||
'project-panel-empty': !cards.length,
|
|
||||||
'project-panel-over-limit': over_limit
|
|
||||||
};
|
|
||||||
var panel = JX.DOM.findAbove(col, 'div', 'workpanel');
|
|
||||||
for (var p in panel_map) {
|
|
||||||
JX.DOM.alterClass(panel, p, !!panel_map[p]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var color_map = {
|
|
||||||
'phui-tag-shade-disabled': (sum === 0),
|
|
||||||
'phui-tag-shade-blue': (sum > 0 && !over_limit),
|
|
||||||
'phui-tag-shade-red': (over_limit)
|
|
||||||
};
|
|
||||||
for (var c in color_map) {
|
|
||||||
JX.DOM.alterClass(data.countTagNode, c, !!color_map[c]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_statics(update_config) {
|
function update_statics(update_config) {
|
||||||
statics.boardID = update_config.boardID;
|
statics.boardID = update_config.boardID;
|
||||||
statics.projectPHID = update_config.projectPHID;
|
statics.projectPHID = update_config.projectPHID;
|
||||||
|
@ -135,7 +87,8 @@ JX.behavior('project-boards', function(config, statics) {
|
||||||
var board_node = JX.$(config.boardID);
|
var board_node = JX.$(config.boardID);
|
||||||
|
|
||||||
var board = statics.workboard.newBoard(board_phid, board_node)
|
var board = statics.workboard.newBoard(board_phid, board_node)
|
||||||
.setOrder(config.order);
|
.setOrder(config.order)
|
||||||
|
.setPointsEnabled(config.pointsEnabled);
|
||||||
|
|
||||||
var templates = config.templateMap;
|
var templates = config.templateMap;
|
||||||
for (var k in templates) {
|
for (var k in templates) {
|
||||||
|
@ -156,6 +109,11 @@ JX.behavior('project-boards', function(config, statics) {
|
||||||
board.setOrderMap(object_phid, order_maps[object_phid]);
|
board.setOrderMap(object_phid, order_maps[object_phid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var property_maps = config.propertyMaps;
|
||||||
|
for (var property_phid in property_maps) {
|
||||||
|
board.setObjectProperties(property_phid, property_maps[property_phid]);
|
||||||
|
}
|
||||||
|
|
||||||
board.start();
|
board.start();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue