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