Skip to content

Commit f03460d

Browse files
authored
Merge pull request #248 from ccsliinc/updated_confirmation
Updated Confirmation Dialog
2 parents d5a96a5 + 42a6491 commit f03460d

File tree

9 files changed

+350
-3
lines changed

9 files changed

+350
-3
lines changed

examples/confirm-cancellable.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
use PhpSchool\CliMenu\CliMenu;
4+
use PhpSchool\CliMenu\Builder\CliMenuBuilder;
5+
6+
require_once(__DIR__ . '/../vendor/autoload.php');
7+
8+
$itemCallable = function (CliMenu $menu) {
9+
$continue = $menu->cancellableConfirm('PHP School FTW!', null, true)
10+
->display('OK', 'Cancel');
11+
12+
if ($continue) {
13+
// Something Destructive
14+
echo "OK";
15+
} else {
16+
// Do nothing
17+
echo "Cancel";
18+
}
19+
};
20+
21+
$menu = (new CliMenuBuilder)
22+
->setTitle('Basic CLI Menu')
23+
->addItem('First Item', $itemCallable)
24+
->addItem('Second Item', $itemCallable)
25+
->addItem('Third Item', $itemCallable)
26+
->addLineBreak('-')
27+
->build();
28+
29+
$menu->open();

src/CliMenu.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpSchool\CliMenu;
44

5+
use PhpSchool\CliMenu\Dialogue\CancellableConfirm;
56
use PhpSchool\CliMenu\Exception\InvalidTerminalException;
67
use PhpSchool\CliMenu\Exception\MenuNotOpenException;
78
use PhpSchool\CliMenu\Input\InputIO;
@@ -708,6 +709,17 @@ public function confirm(string $text, MenuStyle $style = null) : Confirm
708709
return new Confirm($this, $style, $this->terminal, $text);
709710
}
710711

712+
public function cancellableConfirm(string $text, MenuStyle $style = null) : CancellableConfirm
713+
{
714+
$this->guardSingleLine($text);
715+
716+
$style = $style ?? (new MenuStyle($this->terminal))
717+
->setBg('yellow')
718+
->setFg('red');
719+
720+
return new CancellableConfirm($this, $style, $this->terminal, $text);
721+
}
722+
711723
public function askNumber(MenuStyle $style = null) : Number
712724
{
713725
$this->assertOpen();

src/Dialogue/CancellableConfirm.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
namespace PhpSchool\CliMenu\Dialogue;
4+
5+
use PhpSchool\Terminal\InputCharacter;
6+
use PhpSchool\Terminal\NonCanonicalReader;
7+
8+
/**
9+
* @author Aydin Hassan <aydin@hotmail.co.uk>
10+
*/
11+
class CancellableConfirm extends Dialogue
12+
{
13+
/**
14+
* @var bool
15+
*/
16+
private $confirm = true;
17+
18+
/**
19+
* Display confirmation with a button with the given text
20+
*/
21+
public function display(string $confirmText = 'OK', string $cancelText = 'Cancel') : bool
22+
{
23+
$this->drawDialog($confirmText, $cancelText);
24+
25+
$reader = new NonCanonicalReader($this->terminal);
26+
27+
while ($char = $reader->readCharacter()) {
28+
if ($char->isControl() && $char->getControl() === InputCharacter::ENTER) {
29+
$this->parentMenu->redraw();
30+
return $this->confirm;
31+
} elseif ($char->isControl() && $char->getControl() === InputCharacter::TAB ||
32+
($char->isControl() && $char->getControl() === InputCharacter::RIGHT && $this->confirm) ||
33+
($char->isControl() && $char->getControl() === InputCharacter::LEFT && !$this->confirm)
34+
) {
35+
$this->confirm = !$this->confirm;
36+
$this->drawDialog($confirmText, $cancelText);
37+
}
38+
}
39+
}
40+
41+
private function drawDialog(string $confirmText = 'OK', string $cancelText = 'Cancel'): void
42+
{
43+
$this->assertMenuOpen();
44+
45+
$this->terminal->moveCursorToRow($this->y);
46+
47+
$promptWidth = mb_strlen($this->text) + 4;
48+
49+
$buttonLength = mb_strlen($confirmText) + 6;
50+
$buttonLength += mb_strlen($cancelText) + 7;
51+
52+
$confirmButton = sprintf(
53+
'%s%s < %s > %s%s',
54+
$this->style->getOptionCode($this->confirm ? 'bold' : 'dim'),
55+
$this->style->getInvertedColoursSetCode(),
56+
$confirmText,
57+
$this->style->getInvertedColoursUnsetCode(),
58+
$this->style->getOptionCode($this->confirm ? 'bold' : 'dim', false)
59+
);
60+
61+
$cancelButton = sprintf(
62+
'%s%s < %s > %s%s',
63+
$this->style->getOptionCode($this->confirm ? 'dim' : 'bold'),
64+
$this->style->getInvertedColoursSetCode(),
65+
$cancelText,
66+
$this->style->getInvertedColoursUnsetCode(),
67+
$this->style->getOptionCode($this->confirm ? 'dim' : 'bold', false)
68+
);
69+
70+
$buttonRow = $confirmButton . " " . $cancelButton;
71+
72+
if ($promptWidth < $buttonLength) {
73+
$pad = ($buttonLength - $promptWidth) / 2;
74+
$this->text = sprintf(
75+
'%s%s%s',
76+
str_repeat(' ', intval(round($pad, 0, 2) + $this->style->getPaddingLeftRight())),
77+
$this->text,
78+
str_repeat(' ', intval(round($pad, 0, 1) + $this->style->getPaddingLeftRight()))
79+
);
80+
$promptWidth = mb_strlen($this->text) + 4;
81+
}
82+
83+
$leftFill = (int) (($promptWidth / 2) - ($buttonLength / 2));
84+
85+
$this->emptyRow();
86+
87+
$this->write(sprintf(
88+
"%s%s%s%s%s\n",
89+
$this->style->getColoursSetCode(),
90+
str_repeat(' ', $this->style->getPaddingLeftRight()),
91+
$this->text,
92+
str_repeat(' ', $this->style->getPaddingLeftRight()),
93+
$this->style->getColoursResetCode()
94+
));
95+
96+
$this->emptyRow();
97+
98+
$this->write(sprintf(
99+
"%s%s%s%s%s\n",
100+
$this->style->getColoursSetCode(),
101+
str_repeat(' ', $leftFill),
102+
$buttonRow,
103+
str_repeat(' ', (int) ceil($promptWidth - $leftFill - $buttonLength)),
104+
$this->style->getColoursResetCode()
105+
));
106+
107+
$this->emptyRow();
108+
109+
$this->terminal->moveCursorToTop();
110+
}
111+
}

src/Dialogue/Dialogue.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ abstract class Dialogue
3232
*/
3333
protected $text;
3434

35+
/**
36+
* @var bool $cancellable
37+
*/
38+
protected $cancellable;
39+
3540
/**
3641
* @var int
3742
*/
@@ -42,8 +47,12 @@ abstract class Dialogue
4247
*/
4348
protected $y;
4449

45-
public function __construct(CliMenu $parentMenu, MenuStyle $menuStyle, Terminal $terminal, string $text)
46-
{
50+
public function __construct(
51+
CliMenu $parentMenu,
52+
MenuStyle $menuStyle,
53+
Terminal $terminal,
54+
string $text
55+
) {
4756
$this->style = $menuStyle;
4857
$this->terminal = $terminal;
4958
$this->text = $text;

src/Input/InputIO.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ function (string $line) {
108108
},
109109
$lines
110110
)
111-
);
111+
) ? : 0;
112112
}
113113

114114
private function calculateYPosition() : int

src/MenuStyle.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ private function generatePaddingTopBottomRows() : void
509509
);
510510
}
511511

512+
$this->paddingTopBottom = $this->paddingTopBottom >= 0 ? $this->paddingTopBottom : 0;
512513
$this->paddingTopBottomRows = array_fill(0, $this->paddingTopBottom, $paddingRow);
513514
}
514515

@@ -663,6 +664,10 @@ private function generateBorderRows() : void
663664
);
664665
}
665666

667+
$this->borderTopWidth = $this->borderTopWidth >= 0 ? $this->borderTopWidth : 0;
668+
$this->borderBottomWidth = $this->borderBottomWidth >= 0 ? $this->borderBottomWidth : 0;
669+
670+
666671
$this->borderTopRows = array_fill(0, $this->borderTopWidth, $borderRow);
667672
$this->borderBottomRows = array_fill(0, $this->borderBottomWidth, $borderRow);
668673
}
@@ -813,6 +818,20 @@ public function getBorderColourCode() : string
813818
return sprintf("\033[%sm", $borderColourCode);
814819
}
815820

821+
822+
/**
823+
* Get ansi escape sequence for setting or unsetting the specified option code.
824+
*
825+
* @param string $string Option code (bold|dim|underscore|blink|reverse|conceal)
826+
* @param bool $set Whether to set or unset the code
827+
*
828+
* @return string
829+
*/
830+
public function getOptionCode(string $string, bool $set = true): string
831+
{
832+
return sprintf("\033[%sm", self::$availableOptions[$string][$set ? 'set' : 'unset']);
833+
}
834+
816835
/**
817836
* Get a string of given length consisting of 0-9
818837
* eg $length = 15 : 012345678901234

test/Dialogue/ConfirmTest.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,54 @@ public function testConfirmWithOddLengthConfirmAndEvenLengthButton() : void
140140
static::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
141141
}
142142

143+
public function testConfirmCancellableWithShortPrompt(): void
144+
{
145+
$this->terminal
146+
->method('read')
147+
->will($this->onConsecutiveCalls(
148+
"\n",
149+
"\n"
150+
));
151+
152+
$style = $this->getStyle($this->terminal);
153+
154+
$item = new SelectableItem('Item 1', function (CliMenu $menu) {
155+
$menu->cancellableConfirm('PHP', null, true)
156+
->display('OK', 'Cancel');
157+
158+
$menu->close();
159+
});
160+
161+
$menu = new CliMenu('PHP School FTW', [$item], $this->terminal, $style);
162+
$menu->open();
163+
164+
static::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
165+
}
166+
167+
public function testConfirmCancellableWithLongPrompt(): void
168+
{
169+
$this->terminal
170+
->method('read')
171+
->will($this->onConsecutiveCalls(
172+
"\n",
173+
"\n"
174+
));
175+
176+
$style = $this->getStyle($this->terminal);
177+
178+
$item = new SelectableItem('Item 1', function (CliMenu $menu) {
179+
$menu->cancellableConfirm('PHP School Rocks FTW!', null, true)
180+
->display('OK', 'Cancel');
181+
182+
$menu->close();
183+
});
184+
185+
$menu = new CliMenu('PHP School FTW', [$item], $this->terminal, $style);
186+
$menu->open();
187+
188+
static::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
189+
}
190+
143191
public function testConfirmCanOnlyBeClosedWithEnter() : void
144192
{
145193
$this->terminal
@@ -166,6 +214,79 @@ public function testConfirmCanOnlyBeClosedWithEnter() : void
166214
static::assertStringEqualsFile($this->getTestFile(), $this->output->fetch());
167215
}
168216

217+
public function testConfirmOkNonCancellableReturnsTrue()
218+
{
219+
$this->terminal
220+
->method('read')
221+
->will($this->onConsecutiveCalls(
222+
"\n",
223+
'tab',
224+
"\n"
225+
));
226+
227+
$style = $this->getStyle($this->terminal);
228+
229+
$return = '';
230+
231+
$item = new SelectableItem('Item 1', function (CliMenu $menu) use (&$return) {
232+
$return = $menu->cancellableConfirm('PHP School FTW!')
233+
->display('OK');
234+
235+
$menu->close();
236+
});
237+
238+
$menu = new CliMenu('PHP School FTW', [$item], $this->terminal, $style);
239+
$menu->open();
240+
241+
static::assertTrue($return);
242+
}
243+
244+
public function testConfirmOkCancellableReturnsTrue()
245+
{
246+
$this->terminal
247+
->method('read')
248+
->willReturn("\n", "\t", "\t", "\n");
249+
250+
$style = $this->getStyle($this->terminal);
251+
252+
$return = '';
253+
254+
$item = new SelectableItem('Item 1', function (CliMenu $menu) use (&$return) {
255+
$return = $menu->cancellableConfirm('PHP School FTW!')
256+
->display('OK', 'Cancel');
257+
258+
$menu->close();
259+
});
260+
261+
$menu = new CliMenu('PHP School FTW', [$item], $this->terminal, $style);
262+
$menu->open();
263+
264+
static::assertTrue($return);
265+
}
266+
267+
public function testConfirmCancelCancellableReturnsFalse()
268+
{
269+
$this->terminal
270+
->method('read')
271+
->willReturn("\n", "\t", "\n");
272+
273+
$style = $this->getStyle($this->terminal);
274+
275+
$return = '';
276+
277+
$item = new SelectableItem('Item 1', function (CliMenu $menu) use (&$return) {
278+
$return = $menu->cancellableConfirm('PHP School FTW!', null)
279+
->display('OK', 'Cancel');
280+
281+
$menu->close();
282+
});
283+
284+
$menu = new CliMenu('PHP School FTW', [$item], $this->terminal, $style);
285+
$menu->open();
286+
287+
static::assertFalse($return);
288+
}
289+
169290
private function getTestFile() : string
170291
{
171292
return sprintf('%s/../res/%s.txt', __DIR__, $this->getName());

0 commit comments

Comments
 (0)