192 lines
6.3 kB
1
defmodule BlogWeb.KeyloggerLive do
2
use BlogWeb, :live_view
3
import BlogWeb.CoreComponents
4
require Logger
5
alias Phoenix.LiveView.JS
6
7
@meta_attrs [
8
%{name: "title", content: "See what key you are pressing, and have it remembered"},
9
%{
10
name: "description",
11
content:
12
"See what key you are pressing. It also will keep what you type on hand to print if you want"
13
},
14
%{property: "og:title", content: "See what key you are pressing, and have it remembered"},
15
%{
16
property: "og:description",
17
content:
18
"See what key you are pressing. It also will keep what you type on hand to print if you want"
19
},
20
%{property: "og:type", content: "website"}
21
]
22
23
def mount(_params, _session, socket) do
24
{:ok,
25
assign(socket,
26
pressed_key: "",
27
pressed_keys: "",
28
show_modal: true,
29
page_title: "Experiment - sorta typewriter",
30
meta_attrs: @meta_attrs
31
)}
32
end
33
34
def handle_event("keydown", %{"key" => key}, socket) do
35
Logger.info("Key pressed: #{key}")
36
37
pressed_keys =
38
case key do
39
"Backspace" ->
40
# first we reverse the string, and take the firts character
41
<<_last_character::binary-size(1), rest::binary>> =
42
String.reverse(socket.assigns.pressed_keys)
43
44
# then since its reversed, and just that one sliced off, we have
45
# effectively "backspaced" and we can now reverse the list again, and
46
# we have the copy that the user desires, successfuly having reached their
47
# backspace escape hatch
48
String.reverse(rest)
49
50
"Meta" ->
51
# If it is the meta key, they aren't going to be able to
52
# type a character, so we just skip it too
53
socket.assigns.pressed_keys
54
55
"Shift" ->
56
# if its shift, we skip because the character that shift creates comes next,
57
# e.g, shift + A comes along when shift and a are pressed but we just want the A
58
# so, since we know its coming here, we skip the key itself and
59
# trust that the next event will come
60
socket.assigns.pressed_keys
61
62
"Enter" ->
63
socket.assigns.pressed_keys <> "\r\n"
64
65
_ ->
66
socket.assigns.pressed_keys <> key
67
end
68
69
{:noreply,
70
assign(socket, pressed_key: key, pressed_keys: pressed_keys) |> assign(show_modal: false)}
71
end
72
73
def handle_event("toggle_modal", %{"value" => _}, socket) do
74
{:noreply, assign(socket, show_modal: !socket.assigns.show_modal)}
75
end
76
77
def handle_event("toggle_modal", _, socket) do
78
{:noreply, assign(socket, show_modal: !socket.assigns.show_modal)}
79
end
80
81
def render(assigns) do
82
~H"""
83
<style>
84
@keyframes fadeIn {
85
from { opacity: 0; }
86
to { opacity: 1; }
87
}
88
89
#content-of-letter {
90
font-family: "Courier New", Courier, monospace;
91
line-height: 1.5;
92
white-space: pre-wrap;
93
word-wrap: break-word;
94
}
95
96
.text-container {
97
white-space: pre-wrap;
98
font-family: monospace;
99
}
100
101
.letter-animate {
102
display: inline;
103
opacity: 0;
104
animation: fadeIn 0.1s ease-out forwards;
105
}
106
107
/* Hide print-only content during normal viewing */
108
.print-only {
109
display: none;
110
}
111
112
@media print {
113
/* Hide everything except print content */
114
body * {
115
visibility: hidden;
116
}
117
118
/* Show only our print content */
119
.print-only {
120
display: block !important;
121
visibility: visible !important;
122
position: absolute;
123
left: 0;
124
top: 0;
125
width: 100%;
126
padding: 2rem;
127
font-family: "Courier New", Courier, monospace;
128
font-size: 14px;
129
line-height: 1.5;
130
white-space: pre-wrap;
131
color: black;
132
}
133
}
134
</style>
135
<div class="print-only">
136
THIS COPY IS PROVIDED WITH NO COPY AND PASTE AND IS ALL HAND WRITTEN BY YOUR COMMON HUMAN FRIEND {@pressed_keys}
137
</div>
138
<.head_tags meta_attrs={@meta_attrs} page_title={@page_title} />
139
<h1 class="text-[75px]">Pressing: {@pressed_key}</h1>
140
<%= if @show_modal do %>
141
<div
142
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center"
143
phx-click="toggle_modal"
144
>
145
<div class="bg-white p-8 rounded-lg shadow-lg max-w-lg" phx-click-away="toggle_modal">
146
<div class="prose">
147
<p class="font-mono text-gray-800">
148
Write a truly from-the-heart, manual Valentine's letter to your love. <br />
149
This is met to simulate a typewriter. You can type a message out. <br />
150
It Even prints like one, try pressing ctrl/cmd + P, then printing the letter for your love.
151
<br /> Backspace is supported to fix text. As are newlines.
152
153
Otherwise you must type deliberately and precisely.
154
155
If you print preview the page with ctrl/cmd + p, you get a nice format of document to print this and mail it like a letter.
156
<br />
157
<br />
158
It comes with a guarantee from me that you manually typed it on this website character by character, doing the real work.
159
</p>
160
</div>
161
<div class="mt-6 flex justify-end">
162
<button
163
phx-click="toggle_modal"
164
class="px-4 py-2 bg-gray-800 text-gray-100 rounded hover:bg-gray-700 font-mono"
165
>
166
Close
167
</button>
168
</div>
169
</div>
170
</div>
171
<% end %>
172
<div id="content-of-letter" class="mt-4 text-gray-500" phx-window-keydown="keydown">
173
<div class="mb-4">
174
THIS COPY IS PROVIDED WITH NO COPY AND PASTE AND IS ALL HAND WRITTEN BY YOUR COMMON HUMAN FRIEND
175
<div class="text-container">
176
<%= for {char, index} <- String.split(@pressed_keys, "") |> Enum.with_index() do %>
177
<span class="letter-animate" style={"animation-delay: #{index * 0.005}s"}>{char}</span>
178
<% end %>
179
</div>
180
</div>
181
</div>
182
"""
183
end
184
185
def fade_in(js \\ %JS{}) do
186
JS.transition(
187
js,
188
{"transition-all transform ease-out duration-200", "opacity-0 translate-y-2",
189
"opacity-100 translate-y-0"}
190
)
191
end
192
end
193