174 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
@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
58
def handle_event("toggle_modal", _, socket) do
59
{:noreply, assign(socket, show_modal: !socket.assigns.show_modal)}
60
end
61
62
def render(assigns) do
63
~H"""
64
<style>
65
@keyframes fadeIn {
66
from { opacity: 0; }
67
to { opacity: 1; }
68
}
69
70
#content-of-letter {
71
font-family: "Courier New", Courier, monospace;
72
line-height: 1.5;
73
white-space: pre-wrap;
74
word-wrap: break-word;
75
}
76
77
.text-container {
78
white-space: pre-wrap;
79
font-family: monospace;
80
}
81
82
.letter-animate {
83
display: inline;
84
opacity: 0;
85
animation: fadeIn 0.1s ease-out forwards;
86
}
87
88
/* Hide print-only content during normal viewing */
89
.print-only {
90
display: none;
91
}
92
93
@media print {
94
/* Hide everything except print content */
95
body * {
96
visibility: hidden;
97
}
98
99
/* Show only our print content */
100
.print-only {
101
display: block !important;
102
visibility: visible !important;
103
position: absolute;
104
left: 0;
105
top: 0;
106
width: 100%;
107
padding: 2rem;
108
font-family: "Courier New", Courier, monospace;
109
font-size: 14px;
110
line-height: 1.5;
111
white-space: pre-wrap;
112
color: black;
113
}
114
}
115
</style>
116
<div class="print-only">
117
THIS COPY IS PROVIDED WITH NO COPY AND PASTE AND IS ALL HAND WRITTEN BY YOUR COMMON HUMAN FRIEND
118
119
<%= @pressed_keys %>
120
</div>
121
<.head_tags meta_attrs={@meta_attrs} page_title={@page_title} />
122
<h1 class="text-[75px]">Pressing: <%= @pressed_key %></h1>
123
<%= if @show_modal do %>
124
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center" phx-click="toggle_modal">
125
<div class="bg-white p-8 rounded-lg shadow-lg max-w-lg" phx-click-away="toggle_modal">
126
<div class="prose">
127
<p class="font-mono text-gray-800">
128
129
Write a truly from-the-heart, manual Valentine's letter to your love.
130
<br>
131
This is met to simulate a typewriter. You can type a message out.
132
<br>
133
It Even prints like one, try pressing ctrl/cmd + P, then printing the letter for your love.
134
135
<br>
136
Backspace is supported to fix text. As are newlines.
137
138
Otherwise you must type deliberately and precisely.
139
140
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.
141
<br>
142
<br>
143
144
It comes with a guarantee from me that you manually typed it on this website character by character, doing the real work.
145
</p>
146
</div>
147
<div class="mt-6 flex justify-end">
148
<button
149
phx-click="toggle_modal"
150
class="px-4 py-2 bg-gray-800 text-gray-100 rounded hover:bg-gray-700 font-mono"
151
>
152
Close
153
</button>
154
</div>
155
</div>
156
</div>
157
<% end %>
158
<div id="content-of-letter" class="mt-4 text-gray-500" phx-window-keydown="keydown">
159
<div class="mb-4">
160
THIS COPY IS PROVIDED WITH NO COPY AND PASTE AND IS ALL HAND WRITTEN BY YOUR COMMON HUMAN FRIEND
161
<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>
162
</div>
163
</div>
164
165
"""
166
end
167
168
def fade_in(js \\ %JS{}) do
169
JS.transition(js,
170
{"transition-all transform ease-out duration-200",
171
"opacity-0 translate-y-2",
172
"opacity-100 translate-y-0"})
173
end
174
end
175