165 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.1s ease-out forwards;
85
}
86
87
/* Hide print-only content during normal viewing */
88
.print-only {
89
display: none;
90
}
91
92
@media print {
93
/* Hide everything except print content */
94
body * {
95
visibility: hidden;
96
}
97
98
/* Show only our print content */
99
.print-only {
100
display: block !important;
101
visibility: visible !important;
102
position: absolute;
103
left: 0;
104
top: 0;
105
width: 100%;
106
padding: 2rem;
107
font-family: "Courier New", Courier, monospace;
108
font-size: 14px;
109
line-height: 1.5;
110
white-space: pre-wrap;
111
color: black;
112
}
113
}
114
</style>
115
<div class="print-only">
116
THIS COPY IS PROVIDED WITH NO COPY AND PASTE AND IS ALL HAND WRITTEN BY YOUR COMMON HUMAN FRIEND
117
118
<%= @pressed_keys %>
119
</div>
120
<.head_tags meta_attrs={@meta_attrs} page_title={@page_title} />
121
<h1 class="text-[75px]">Pressing: <%= @pressed_key %></h1>
122
<%= if @show_modal do %>
123
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center" phx-click="toggle_modal">
124
<div class="bg-white p-8 rounded-lg shadow-lg max-w-lg" phx-click-away="toggle_modal">
125
<div class="prose">
126
<p class="font-mono text-gray-800">
127
This is met to simulate a typewriter. You can type a message out.
128
129
Backspace is supported to fix text. As are newlines.
130
131
Otherwise you must type deliberately and precisely.
132
133
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.
134
135
It comes with a guarantee from me that you manually typed it on this website character by character, doing the real work.
136
</p>
137
</div>
138
<div class="mt-6 flex justify-end">
139
<button
140
phx-click="toggle_modal"
141
class="px-4 py-2 bg-gray-800 text-gray-100 rounded hover:bg-gray-700 font-mono"
142
>
143
Close
144
</button>
145
</div>
146
</div>
147
</div>
148
<% end %>
149
<div id="content-of-letter" class="mt-4 text-gray-500" phx-window-keydown="keydown">
150
<div class="mb-4">
151
THIS COPY IS PROVIDED WITH NO COPY AND PASTE AND IS ALL HAND WRITTEN BY YOUR COMMON HUMAN FRIEND
152
<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>
153
</div>
154
</div>
155
156
"""
157
end
158
159
def fade_in(js \\ %JS{}) do
160
JS.transition(js,
161
{"transition-all transform ease-out duration-200",
162
"opacity-0 translate-y-2",
163
"opacity-100 translate-y-0"})
164
end
165
end
166