Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F4503041
button.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
4 KB
Referenced Files
None
Subscribers
None
button.js
View Options
const
ESC
=
'\x1b'
;
const
CSI
=
`
${
ESC
}
[`
;
const
codes
=
{
altScreenOn
:
`
${
CSI
}
?1049h`
,
altScreenOff
:
`
${
CSI
}
?1049l`
,
hideCursor
:
`
${
CSI
}
?25l`
,
showCursor
:
`
${
CSI
}
?25h`
,
clear
:
`
${
CSI
}
2J`
,
home
:
`
${
CSI
}
H`
,
reset
:
`
${
CSI
}
0m`
,
mouseOn
:
`
${
CSI
}
?1000h
${
CSI
}
?1003h
${
CSI
}
?1006h`
,
mouseOff
:
`
${
CSI
}
?1000l
${
CSI
}
?1003l
${
CSI
}
?1006l`
,
invert
:
`
${
CSI
}
7m`
,
fg
:
n
=>
`
${
CSI
}
38;5;
${
n
}
m`
,
bg
:
n
=>
`
${
CSI
}
48;5;
${
n
}
m`
};
const
state
=
{
hover
:
false
,
pressed
:
false
,
message
:
'Hover or click the button'
};
const
button
=
{
label
:
'Click Me'
,
x
:
0
,
y
:
0
,
width
:
0
,
height
:
1
};
const
styles
=
{
normal
:
''
,
hover
:
`
${
codes
.
bg
(
46
)
}${
codes
.
fg
(
16
)
}
`
,
active
:
`
${
codes
.
bg
(
196
)
}${
codes
.
fg
(
15
)
}
`
};
function
write
(
text
)
{
process
.
stdout
.
write
(
text
);
}
function
centerButton
(
width
,
height
)
{
button
.
width
=
button
.
label
.
length
+
4
;
button
.
x
=
Math
.
max
(
0
,
Math
.
floor
((
width
-
button
.
width
)
/
2
));
button
.
y
=
Math
.
max
(
0
,
Math
.
floor
((
height
-
button
.
height
)
/
2
));
}
function
createGrid
(
width
,
height
)
{
const
chars
=
Array
(
height
).
fill
(
null
).
map
(()
=>
Array
(
width
).
fill
(
' '
));
const
stylesGrid
=
Array
(
height
).
fill
(
null
).
map
(()
=>
Array
(
width
).
fill
(
''
));
return
{
chars
,
styles
:
stylesGrid
};
}
function
setCell
(
grid
,
x
,
y
,
ch
,
style
)
{
if
(
y
<
0
||
y
>=
grid
.
chars
.
length
)
return
;
if
(
x
<
0
||
x
>=
grid
.
chars
[
y
].
length
)
return
;
grid
.
chars
[
y
][
x
]
=
ch
;
grid
.
styles
[
y
][
x
]
=
style
;
}
function
setText
(
grid
,
x
,
y
,
text
,
style
)
{
for
(
let
i
=
0
;
i
<
text
.
length
;
i
++
)
{
setCell
(
grid
,
x
+
i
,
y
,
text
[
i
],
style
);
}
}
function
drawButton
(
grid
)
{
const
fillStyle
=
state
.
pressed
?
styles
.
active
:
state
.
hover
?
styles
.
hover
:
styles
.
normal
;
for
(
let
row
=
0
;
row
<
button
.
height
;
row
++
)
{
for
(
let
col
=
0
;
col
<
button
.
width
;
col
++
)
{
setCell
(
grid
,
button
.
x
+
col
,
button
.
y
+
row
,
' '
,
fillStyle
);
}
}
const
padTotal
=
Math
.
max
(
0
,
button
.
width
-
button
.
label
.
length
);
const
padLeft
=
Math
.
floor
(
padTotal
/
2
);
const
labelX
=
button
.
x
+
padLeft
;
const
labelY
=
button
.
y
+
Math
.
floor
(
button
.
height
/
2
);
setText
(
grid
,
labelX
,
labelY
,
button
.
label
,
fillStyle
);
}
function
render
()
{
const
width
=
process
.
stdout
.
columns
||
80
;
const
height
=
process
.
stdout
.
rows
||
24
;
centerButton
(
width
,
height
);
const
grid
=
createGrid
(
width
,
height
);
const
header
=
' Simple Button TUI (q to quit)'
;
setText
(
grid
,
0
,
0
,
header
.
slice
(
0
,
width
),
''
);
const
message
=
state
.
message
;
const
messageLine
=
Math
.
min
(
height
-
2
,
button
.
y
+
button
.
height
+
1
);
if
(
messageLine
>=
0
&&
messageLine
<
height
)
{
setText
(
grid
,
1
,
messageLine
,
message
.
slice
(
0
,
width
-
2
),
''
);
}
drawButton
(
grid
);
const
rows
=
grid
.
chars
.
map
((
row
,
y
)
=>
{
let
line
=
''
;
let
currentStyle
=
''
;
for
(
let
x
=
0
;
x
<
row
.
length
;
x
++
)
{
const
nextStyle
=
grid
.
styles
[
y
][
x
];
if
(
nextStyle
!==
currentStyle
)
{
line
+=
nextStyle
||
codes
.
reset
;
currentStyle
=
nextStyle
;
}
line
+=
row
[
x
];
}
return
line
+
codes
.
reset
;
});
write
(
codes
.
home
+
codes
.
clear
+
rows
.
join
(
'\n'
)
+
codes
.
reset
);
}
function
isInsideButton
(
x
,
y
)
{
return
(
x
>=
button
.
x
&&
x
<
button
.
x
+
button
.
width
&&
y
>=
button
.
y
&&
y
<
button
.
y
+
button
.
height
);
}
function
parseMouseEvent
(
seq
)
{
const
match
=
seq
.
match
(
/^\x1b\[<([0-9]+);([0-9]+);([0-9]+)([mM])$/
);
if
(
!
match
)
return
false
;
const
[,
codeStr
,
xStr
,
yStr
,
action
]
=
match
;
const
code
=
Number
(
codeStr
);
const
x
=
Number
(
xStr
)
-
1
;
const
y
=
Number
(
yStr
)
-
1
;
const
buttonCode
=
code
&
3
;
const
inside
=
isInsideButton
(
x
,
y
);
const
prevHover
=
state
.
hover
;
state
.
hover
=
inside
;
if
(
action
===
'M'
&&
buttonCode
===
0
&&
inside
)
{
state
.
pressed
=
true
;
state
.
message
=
'Button pressed! Release to click.'
;
}
else
if
(
action
===
'm'
)
{
if
(
state
.
pressed
&&
inside
)
{
state
.
message
=
'Button clicked!'
;
}
state
.
pressed
=
false
;
}
if
(
inside
&&
!
prevHover
&&
action
===
'M'
&&
(
code
&
32
))
{
state
.
message
=
'Hovering on the button.'
;
}
if
(
!
inside
&&
prevHover
&&
action
===
'M'
&&
(
code
&
32
))
{
state
.
message
=
'Hover or click the button'
;
}
return
true
;
}
function
handleInput
(
chunk
)
{
const
str
=
chunk
.
toString
();
if
(
str
===
'q'
||
str
===
'\x03'
)
{
cleanup
();
return
;
}
if
(
str
.
startsWith
(
'\x1b[<'
))
{
if
(
parseMouseEvent
(
str
))
{
render
();
}
}
}
function
cleanup
()
{
if
(
process
.
stdin
.
isTTY
)
{
process
.
stdin
.
setRawMode
(
false
);
}
process
.
stdin
.
pause
();
process
.
stdin
.
removeListener
(
'data'
,
handleInput
);
process
.
stdout
.
removeListener
(
'resize'
,
render
);
write
(
codes
.
mouseOff
+
codes
.
showCursor
+
codes
.
altScreenOff
);
process
.
exit
(
0
);
}
function
start
()
{
if
(
process
.
stdin
.
isTTY
)
{
process
.
stdin
.
setRawMode
(
true
);
}
process
.
stdin
.
resume
();
process
.
stdin
.
on
(
'data'
,
handleInput
);
process
.
stdout
.
on
(
'resize'
,
render
);
write
(
codes
.
altScreenOn
+
codes
.
hideCursor
+
codes
.
mouseOn
);
render
();
}
process
.
on
(
'SIGINT'
,
cleanup
);
process
.
on
(
'SIGTERM'
,
cleanup
);
start
();
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, May 3, 9:20 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
518039
Default Alt Text
button.js (4 KB)
Attached To
Mode
rANT Ant
Attached
Detach File
Event Timeline
Log In to Comment