Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
What's new
4
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Switch to GitLab Next
Sign in / Register
Toggle navigation
Menu
Open sidebar
rva-vzw
wdebelek
Commits
e1b6d16e
Commit
e1b6d16e
authored
Jan 19, 2021
by
Johan Vervloet
Browse files
Replaced old CurrentTricks by new TricksAtTables,
#177
.
parent
cc8b2886
Pipeline
#244001427
failed with stages
in 6 minutes and 19 seconds
Changes
15
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
assets/js/Components/CardMat.vue
View file @
e1b6d16e
...
...
@@ -399,10 +399,7 @@
},
updateTableState
()
{
return
tableApi
.
getTableState
(
this
.
tableId
).
then
(
result
=>
{
this
.
currentTrick
=
result
.
currentTrick
;
this
.
trickNumber
=
result
.
trickNumber
;
this
.
cardPlayed
=
this
.
currentTrick
&&
this
.
playerId
in
this
.
currentTrick
?
[
this
.
currentTrick
[
this
.
playerId
]]
:
[];
this
.
gameId
=
result
.
gameIdentifier
;
this
.
conclusionVoteCount
=
result
.
doneVotes
+
result
.
redealVotes
;
this
.
firstGame
=
result
.
firstGame
;
...
...
@@ -602,6 +599,13 @@
getPlayerNameById
(
playerId
)
{
let
player
=
this
.
getPlayerById
(
playerId
);
return
player
?
player
.
name
:
'
(???)
'
;
},
updateCurrentTrick
()
{
return
trickApi
.
getCurrentTrick
(
this
.
tableId
).
then
((
result
)
=>
{
this
.
currentTrick
=
result
;
this
.
cardPlayed
=
this
.
currentTrick
&&
this
.
playerId
in
this
.
currentTrick
?
[
this
.
currentTrick
[
this
.
playerId
]]
:
[];
})
}
},
computed
:
{
...
...
@@ -711,8 +715,9 @@
this
.
refreshMyCards
();
this
.
updateRevealedCards
();
break
;
case
"
App
\\
Domain
\\
WriteModel
\\
Game
\\
Event
\\
CardsCutAfter
"
:
case
"
App
\\
Domain
\\
WriteModel
\\
Game
\\
Event
\\
TrickWon
"
:
this
.
updateCurrentTrick
();
case
"
App
\\
Domain
\\
WriteModel
\\
Game
\\
Event
\\
CardsCutAfter
"
:
// need updating table state so that game number gets through when GameStarted.
// need updating players for trick count (TrickWon)
this
.
updateTableState
().
then
(
res
=>
...
...
@@ -754,9 +759,8 @@
// up the dealing strategy dialog.
this
.
updateTableState
();
break
;
default
:
// This could probably use some refinement:
this
.
updateTableState
();
default
:
this
.
updateCurrentTrick
();
this
.
updateRevealedCards
();
// Number of cards in hands typically updates when playing/picking up a card.
this
.
updatePlayingPlayers
();
...
...
assets/js/api/trickApi.js
View file @
e1b6d16e
...
...
@@ -15,10 +15,10 @@ export default {
'
card
'
:
cardNumber
});
},
getCurrentTrick
(
gam
eId
)
{
getCurrentTrick
(
tabl
eId
)
{
let
url
=
routing
.
getRoute
(
'
wdebelek.api.current_trick
'
,
{
gam
eId
:
gam
eId
tabl
eId
:
tabl
eId
}
);
...
...
@@ -37,22 +37,6 @@ export default {
return
axios
.
delete
(
url
);
},
// Not sure this is the correct way to get the url of the mercure hub...
getHubUrl
(
gameId
)
{
let
url
=
routing
.
getRoute
(
'
wdebelek.api.current_trick
'
,
{
gameId
:
gameId
}
);
return
axios
.
get
(
url
).
then
(
response
=>
{
console
.
log
(
response
);
const
hubUrl
=
response
.
headers
.
link
.
match
(
/<
([^
>
]
+
)
>;
\s
+rel=
(?:
mercure|"
[^
"
]
*mercure
[^
"
]
*"
)
/
)[
1
];
console
.
log
(
hubUrl
);
return
hubUrl
;
})
},
voteWinner
(
gameId
,
trickNumber
,
voterSecret
,
winnerId
)
{
let
url
=
routing
.
getRoute
(
'
wdebelek.api.vote_winner
'
,
{
...
...
src/Controller/TrickApiController.php
View file @
e1b6d16e
...
...
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace
App\Controller
;
use
App\Domain\ReadModel\T
ableState\CurrentTrick
s
;
use
App\Domain\ReadModel\T
rickAtTable\TricksAtTable
s
;
use
App\Domain\ValueObject\Card\Card
;
use
App\Domain\ValueObject\Player\PlayerIdentifier
;
use
App\Domain\ValueObject\Secret
;
...
...
@@ -13,6 +13,7 @@ use App\Domain\WriteModel\Game\Command\PlayCard;
use
App\Domain\WriteModel\Game\Command\ReviewPreviousTrick
;
use
App\Domain\WriteModel\Game\Command\VoteAsWinner
;
use
App\Domain\WriteModel\Game\GameIdentifier
;
use
App\Domain\WriteModel\Table\TableIdentifier
;
use
RvaVzw\KrakBoem\Cqrs\CommandBus\CommandBus
;
use
Symfony\Bundle\FrameworkBundle\Controller\AbstractController
;
use
Symfony\Component\HttpFoundation\JsonResponse
;
...
...
@@ -69,11 +70,11 @@ final class TrickApiController extends AbstractController
}
/**
* @Route("/api/
game/{gam
eId}/trick", name="wdebelek.api.current_trick", methods="GET", options={"expose"=true})
* @Route("/api/
table/{tabl
eId}/trick", name="wdebelek.api.current_trick", methods="GET", options={"expose"=true})
*/
public
function
getTrick
(
string
$
gam
eId
,
CurrentTricks
$currentTrick
s
,
string
$
tabl
eId
,
TricksAtTables
$tricksAtTable
s
,
Request
$request
):
Response
{
// This parameter is automatically created by the MercureBundle
...
...
@@ -83,7 +84,7 @@ final class TrickApiController extends AbstractController
$this
->
addLink
(
$request
,
new
Link
(
'mercure'
,
$hubUrl
));
$result
=
$this
->
normalizer
->
normalize
(
$
currentTricks
->
getCurrentTrick
(
Gam
eIdentifier
::
fromString
(
$
gam
eId
))
$
tricksAtTables
->
getTrickAtTable
(
Tabl
eIdentifier
::
fromString
(
$
tabl
eId
))
);
return
new
JsonResponse
(
$result
,
Response
::
HTTP_OK
);
...
...
src/Domain/ReadModel/TableState/CurrentTricks.php
deleted
100644 → 0
View file @
cc8b2886
<?php
declare
(
strict_types
=
1
);
namespace
App\Domain\ReadModel\TableState
;
use
App\Domain\ValueObject\Game\Trick
;
use
App\Domain\WriteModel\Game\GameIdentifier
;
interface
CurrentTricks
{
public
function
getCurrentTrick
(
GameIdentifier
$gameIdentifier
):
Trick
;
}
src/Domain/ReadModel/TableState/TableState.php
View file @
e1b6d16e
...
...
@@ -6,7 +6,6 @@ namespace App\Domain\ReadModel\TableState;
use
App\Domain\ValueObject\Card\Card
;
use
App\Domain\ValueObject\Game\GameConclusion
;
use
App\Domain\ValueObject\Game\Trick
;
use
App\Domain\ValueObject\Player\Exception\NoTrumpCard
;
use
App\Domain\ValueObject\Player\Exception\PlayerNotFound
;
use
App\Domain\ValueObject\Player\PlayerIdentifier
;
...
...
@@ -29,7 +28,6 @@ final class TableState
{
private
?GameIdentifier
$gameIdentifier
;
private
int
$trickNumber
;
private
Trick
$currentTrick
;
private
TableIdentifier
$tableIdentifier
;
private
int
$doneVotes
;
private
int
$redealVotes
;
...
...
@@ -46,7 +44,6 @@ final class TableState
public
function
__construct
(
?GameIdentifier
$gameIdentifier
,
int
$trickNumber
,
Trick
$currentTrick
,
TableIdentifier
$tableIdentifier
,
int
$doneVotes
,
int
$redealVotes
,
...
...
@@ -62,7 +59,6 @@ final class TableState
)
{
$this
->
gameIdentifier
=
$gameIdentifier
;
$this
->
trickNumber
=
$trickNumber
;
$this
->
currentTrick
=
$currentTrick
;
$this
->
tableIdentifier
=
$tableIdentifier
;
$this
->
doneVotes
=
$doneVotes
;
$this
->
redealVotes
=
$redealVotes
;
...
...
@@ -82,7 +78,6 @@ final class TableState
return
new
self
(
null
,
0
,
Trick
::
empty
(),
$tableIdentifier
,
0
,
0
,
...
...
@@ -106,7 +101,6 @@ final class TableState
$result
=
clone
$this
;
$result
->
gameIdentifier
=
$gameIdentifier
;
$result
->
trickNumber
=
1
;
$result
->
currentTrick
=
Trick
::
empty
();
$result
->
doneVotes
=
0
;
$result
->
redealVotes
=
0
;
$result
->
playerTeams
=
Teams
::
allIndividual
(
$players
);
...
...
@@ -134,11 +128,6 @@ final class TableState
return
$this
->
trickNumber
;
}
public
function
getCurrentTrick
():
Trick
{
return
$this
->
currentTrick
;
}
public
function
getTableIdentifier
():
TableIdentifier
{
return
$this
->
tableIdentifier
;
...
...
@@ -200,24 +189,6 @@ final class TableState
return
$result
;
}
public
function
withPlayedCard
(
PlayerIdentifier
$playerIdentifier
,
Card
$card
):
self
{
$result
=
clone
$this
;
$result
->
currentTrick
=
$result
->
currentTrick
->
withPlayedCard
(
$playerIdentifier
,
$card
);
return
$result
;
}
public
function
withCardPickedUp
(
PlayerIdentifier
$playerIdentifier
,
Card
$card
):
self
{
$result
=
clone
$this
;
$result
->
currentTrick
=
$result
->
currentTrick
->
withCardWithdrawn
(
$playerIdentifier
);
return
$result
;
}
public
function
getPlayerTeams
():
Teams
{
return
$this
->
playerTeams
;
...
...
src/Domain/ReadModel/TableState/TableStateProjector.php
View file @
e1b6d16e
...
...
@@ -5,8 +5,6 @@ declare(strict_types=1);
namespace
App\Domain\ReadModel\TableState
;
use
App\Domain\ReadModel\PlayersAtTable\PlayersAtTable
;
use
App\Domain\WriteModel\Game\Event\CardPickedUp
;
use
App\Domain\WriteModel\Game\Event\CardPlayed
;
use
App\Domain\WriteModel\Game\Event\CardsCutAfter
;
use
App\Domain\WriteModel\Game\Event\ConclusionVoteInvalidated
;
use
App\Domain\WriteModel\Game\Event\Dealt
;
...
...
@@ -51,28 +49,6 @@ final class TableStateProjector extends AbstractProjector
$this
->
playersAtTable
=
$playersAtTable
;
}
public
function
applyCardPlayed
(
CardPlayed
$event
):
void
{
$gameState
=
$this
->
tableStateRepository
->
getTableStateByGame
(
$event
->
getGameIdentifier
());
$this
->
tableStateRepository
->
saveTableState
(
$gameState
->
withPlayedCard
(
$event
->
getPlayerIdentifier
(),
$event
->
getCard
()
)
);
}
public
function
applyCardPickedUp
(
CardPickedUp
$event
):
void
{
$tableState
=
$this
->
tableStateRepository
->
getTableStateByGame
(
$event
->
getGameIdentifier
());
$this
->
tableStateRepository
->
saveTableState
(
$tableState
->
withCardPickedUp
(
$event
->
getPlayerIdentifier
(),
$event
->
getCard
()
)
);
}
public
function
applyTrickWon
(
TrickWon
$event
):
void
{
$this
->
tableStateUpdater
->
logTrick
(
...
...
src/Domain/ReadModel/TrickAtTable/TrickAtTableRepository.php
View file @
e1b6d16e
...
...
@@ -10,4 +10,4 @@ use App\Domain\WriteModel\Table\TableIdentifier;
interface
TrickAtTableRepository
extends
TricksAtTables
{
public
function
replaceTrickAtTable
(
TableIdentifier
$tableIdentifier
,
Trick
$trick
):
void
;
}
\ No newline at end of file
}
src/Domain/ReadModel/TrickAtTable/TricksAtTables.php
View file @
e1b6d16e
...
...
@@ -9,6 +9,5 @@ use App\Domain\WriteModel\Table\TableIdentifier;
interface
TricksAtTables
{
public
function
getTrickAtTable
(
TableIdentifier
$tableIdentifier
):
Trick
;
}
\ No newline at end of file
}
src/Orm/Entity/OrmTableState.php
View file @
e1b6d16e
...
...
@@ -29,12 +29,6 @@ class OrmTableState
*/
private
?string
$gameId
;
/**
* @var mixed[]
* @ORM\Column(type="json")
*/
private
array
$normalizedTrick
;
/**
* @ORM\Column(type="integer")
*/
...
...
@@ -103,12 +97,10 @@ class OrmTableState
private
int
$totalNumberOfCards
;
/**
* @param int[] $normalizedCards
* @param mixed[] $normalizedTeams
*/
private
function
__construct
(
?string
$gameId
,
array
$normalizedCards
,
int
$trickNumber
,
string
$tableId
,
int
$doneVotes
,
...
...
@@ -124,7 +116,6 @@ class OrmTableState
int
$totalNumberOfCards
)
{
$this
->
gameId
=
$gameId
;
$this
->
normalizedTrick
=
$normalizedCards
;
$this
->
trickNumber
=
$trickNumber
;
$this
->
tableId
=
$tableId
;
$this
->
doneVotes
=
$doneVotes
;
...
...
@@ -140,22 +131,6 @@ class OrmTableState
$this
->
totalNumberOfCards
=
$totalNumberOfCards
;
}
/**
* @return mixed[]
*/
public
function
getNormalizedTrick
():
array
{
return
$this
->
normalizedTrick
;
}
/**
* @param mixed[] $normalizedCards
*/
public
function
replaceNormalizedTrick
(
array
$normalizedCards
):
void
{
$this
->
normalizedTrick
=
$normalizedCards
;
}
public
function
setTrickNumber
(
int
$trickNumber
):
void
{
$this
->
trickNumber
=
$trickNumber
;
...
...
@@ -170,7 +145,6 @@ class OrmTableState
{
return
new
self
(
null
,
[],
0
,
$tableIdentifier
->
toString
(),
0
,
...
...
@@ -268,7 +242,6 @@ class OrmTableState
{
// If the table is suspended, the game is instantly aborted, so no more game info here.
$this
->
gameId
=
null
;
$this
->
normalizedTrick
=
[];
$this
->
trickNumber
=
0
;
$this
->
doneVotes
=
0
;
$this
->
redealVotes
=
0
;
...
...
src/Orm/Entity/OrmTrickAtTable.php
View file @
e1b6d16e
...
...
@@ -61,4 +61,4 @@ class OrmTrickAtTable
{
$this
->
tableId
=
$tableIdentifier
->
toString
();
}
}
\ No newline at end of file
}
src/Orm/ReadModel/TableStateReadModel.php
View file @
e1b6d16e
...
...
@@ -8,7 +8,6 @@ use App\Domain\ReadModel\CardGames\TableCardGames;
use
App\Domain\ReadModel\Exception\NotFoundInReadModel
;
use
App\Domain\ReadModel\LastPlayers\LastPlayers
;
use
App\Domain\ReadModel\TableState\ActiveTables
;
use
App\Domain\ReadModel\TableState\CurrentTricks
;
use
App\Domain\ReadModel\TableState\GamesAtTable
;
use
App\Domain\ReadModel\TableState\SuspendedTables
;
use
App\Domain\ReadModel\TableState\TableState
;
...
...
@@ -17,7 +16,6 @@ use App\Domain\ReadModel\TableState\TableStates;
use
App\Domain\ReadModel\TableState\TableStateUpdater
;
use
App\Domain\ReadModel\TableState\TeamsByGame
;
use
App\Domain\ValueObject\Card\Card
;
use
App\Domain\ValueObject\Game\Trick
;
use
App\Domain\ValueObject\Player\PlayerIdentifier
;
use
App\Domain\ValueObject\Player\Teams
;
use
App\Domain\ValueObject\Table\CardGame
;
...
...
@@ -35,7 +33,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
*
* FIXME (#106): This read model has too many responsibilities.
*/
final
class
TableStateReadModel
implements
CurrentTricks
,
TableStateUpdater
,
TableStates
,
ActiveTables
,
TableStateRepository
,
SuspendedTables
,
GamesAtTable
,
TeamsByGame
,
TableCardGames
,
LastPlayers
final
class
TableStateReadModel
implements
TableStateUpdater
,
TableStates
,
ActiveTables
,
TableStateRepository
,
SuspendedTables
,
GamesAtTable
,
TeamsByGame
,
TableCardGames
,
LastPlayers
{
/** @var TableStateOrmRepository */
private
$repository
;
...
...
@@ -54,26 +52,12 @@ final class TableStateReadModel implements CurrentTricks, TableStateUpdater, Tab
$this
->
denormalizer
=
$denormalizer
;
}
public
function
getCurrentTrick
(
GameIdentifier
$gameIdentifier
):
Trick
{
$entity
=
$this
->
getOrmTableState
(
$gameIdentifier
);
/** @var Trick $result */
$result
=
$this
->
denormalizer
->
denormalize
(
$entity
->
getNormalizedTrick
(),
Trick
::
class
);
return
$result
;
}
public
function
logTrick
(
GameIdentifier
$gameIdentifier
,
int
$trickNumber
,
PlayerIdentifier
$winner
):
void
{
$entity
=
$this
->
getOrmTableState
(
$gameIdentifier
);
$this
->
replaceTrick
(
$entity
,
Trick
::
empty
());
$entity
->
setTrickNumber
(
$trickNumber
+
1
);
$this
->
repository
->
save
(
$entity
);
}
...
...
@@ -92,25 +76,6 @@ final class TableStateReadModel implements CurrentTricks, TableStateUpdater, Tab
throw
NotFoundInReadModel
::
create
(
TableStateReadModel
::
class
,
$gameIdentifier
);
}
private
function
getTrickFromEntity
(
OrmTableState
$entity
):
Trick
{
/** @var Trick $trick */
$trick
=
$this
->
denormalizer
->
denormalize
(
$entity
->
getNormalizedTrick
(),
Trick
::
class
);
return
$trick
;
}
private
function
replaceTrick
(
OrmTableState
$entity
,
Trick
$trick
):
void
{
/** @var array<string,int> $normalizedTrick */
$normalizedTrick
=
$this
->
normalizer
->
normalize
(
$trick
);
$entity
->
replaceNormalizedTrick
(
$normalizedTrick
);
}
public
function
deleteTableState
(
TableIdentifier
$tableIdentifier
):
void
{
$this
->
repository
->
deleteByTableIdentifier
(
$tableIdentifier
);
...
...
@@ -135,7 +100,6 @@ final class TableStateReadModel implements CurrentTricks, TableStateUpdater, Tab
$isFirstGame
=
!
$entity
->
isActive
();
$entity
->
setGameIdentifier
(
$gameIdentifier
);
$entity
->
resume
();
$entity
->
replaceNormalizedTrick
([]);
$entity
->
setDealer
(
$dealerIdentifier
);
$entity
->
setLastPlayer
(
$lastPlayer
);
...
...
@@ -190,9 +154,6 @@ final class TableStateReadModel implements CurrentTricks, TableStateUpdater, Tab
private
function
mapToOrm
(
TableState
$tableState
,
OrmTableState
$ormTableState
):
void
{
$ormTableState
->
setGameIdentifier
(
$tableState
->
isActive
()
?
$tableState
->
getGameIdentifier
()
:
null
);
/** @var int[] $normalizedCards */
$normalizedCards
=
$this
->
normalizer
->
normalize
(
$tableState
->
getCurrentTrick
());
$ormTableState
->
replaceNormalizedTrick
(
$normalizedCards
);
$ormTableState
->
setTrickNumber
(
$tableState
->
getTrickNumber
());
$ormTableState
->
setDoneVotes
(
$tableState
->
getDoneVotes
());
$ormTableState
->
setRedealVotes
(
$tableState
->
getRedealVotes
());
...
...
@@ -230,7 +191,6 @@ final class TableStateReadModel implements CurrentTricks, TableStateUpdater, Tab
return
new
TableState
(
$gameIdentifier
,
$entity
->
getTrickNumber
(),
$this
->
getTrickFromEntity
(
$entity
),
$entity
->
getTableIdentifier
(),
$entity
->
getDoneVotes
(),
$entity
->
getRedealVotes
(),
...
...
src/Orm/Repository/TrickAtTableOrmRepository.php
View file @
e1b6d16e
...
...
@@ -67,4 +67,4 @@ final class TrickAtTableOrmRepository extends ServiceEntityRepository implements
$this
->
_em
->
persist
(
$ormTrickAtTable
);
$this
->
_em
->
flush
();
}
}
\ No newline at end of file
}
tests/integration/Domain/ReadModel/TableState/TableStateProjectorTest.php
View file @
e1b6d16e
...
...
@@ -199,7 +199,6 @@ final class TableStateProjectorTest extends KernelTestCase
$expected
=
new
TableState
(
$gameIdentifier
,
1
,
Trick
::
empty
()
->
withPlayedCard
(
TestPlayers
::
penningmeester
(),
new
Card
(
12
,
Card
::
HEARTS
)),
$this
->
tableIdentifier
,
0
,
0
,
...
...
@@ -289,7 +288,6 @@ final class TableStateProjectorTest extends KernelTestCase
$expected
=
new
TableState
(
$gameIdentifier
,
1
,
Trick
::
empty
(),
$this
->
tableIdentifier
,
0
,
0
,
...
...
@@ -336,7 +334,6 @@ final class TableStateProjectorTest extends KernelTestCase
GameIdentifier
::
fromNumber
(
$this
->
tableIdentifier
,
GameNumber
::
first
()),
// trick number is only set when game starts.
0
,
Trick
::
empty
(),
$this
->
tableIdentifier
,
0
,
0
,
...
...
tests/unit/Domain/ReadModel/TableState/TableStateProjectorTest.php
View file @
e1b6d16e
...
...
@@ -10,7 +10,6 @@ use App\Domain\ReadModel\TableState\TableStateUpdater;
use
App\Domain\ValueObject\Card\Card
;
use
App\Domain\ValueObject\Card\SimplePileOfCards
;
use
App\Domain\ValueObject\Game\GameNumber
;
use
App\Domain\ValueObject\Game\Trick
;
use
App\Domain\ValueObject\Player\SimplePlayerIdentifiers
;
use
App\Domain\ValueObject\Player\Teams
;
use
App\Domain\ValueObject\Secret
;
...
...
@@ -69,7 +68,6 @@ final class TableStateProjectorTest extends TestCase
$initialTableState
=
new
TableState
(
$gameIdentifier
,
1
,
Trick
::
empty
()
->
withPlayedCard
(
TestPlayers
::
penningmeester
(),
new
Card
(
12
,
Card
::
HEARTS
)),
$tableIdentifier
,
0
,
0
,
...
...
@@ -87,7 +85,6 @@ final class TableStateProjectorTest extends TestCase
$expectedTableState
=
new
TableState
(
$gameIdentifier
,
1
,
Trick
::
empty
(),
$tableIdentifier
,
0
,
0
,
...
...
tests/unit/Domain/ReadModel/TableState/TableStateTest.php
View file @
e1b6d16e
...
...
@@ -5,7 +5,6 @@ namespace App\Tests\Unit\Domain\ReadModel\TableState;
use
App\Domain\ReadModel\TableState\TableState
;
use
App\Domain\ValueObject\Card\Card
;
use
App\Domain\ValueObject\Game\GameNumber
;
use
App\Domain\ValueObject\Game\Trick
;
use
App\Domain\ValueObject\Player\SimplePlayerIdentifiers
;
use
App\Domain\ValueObject\Player\Teams
;
use
App\Domain\ValueObject\Secret
;
...
...
@@ -76,7 +75,6 @@ final class TableStateTest extends TestCase
$expected
=
new
TableState
(
$gameIdentifier
,
1
,
Trick
::
empty
(),
$tableIdentifier
,
0
,
0
,
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment