172 lines
6.1 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
133
<br>
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
<br>
140
<br>
141
142
It comes with a guarantee from me that you manually typed it on this website character by character, doing the real work.
143
</p>
144
</div>
145
<div class="mt-6 flex justify-end">
146
<button
147
phx-click="toggle_modal"
148
class="px-4 py-2 bg-gray-800 text-gray-100 rounded hover:bg-gray-700 font-mono"
149
>
150
Close
151
</button>
152
</div>
153
</div>
154
</div>
155
<% end %>
156
<div id="content-of-letter" class="mt-4 text-gray-500" phx-window-keydown="keydown">
157
<div class="mb-4">
158
THIS COPY IS PROVIDED WITH NO COPY AND PASTE AND IS ALL HAND WRITTEN BY YOUR COMMON HUMAN FRIEND
159
<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>
160
</div>
161
</div>
162
163
"""
164
end
165
166
def fade_in(js \\ %JS{}) do
167
JS.transition(js,
168
{"transition-all transform ease-out duration-200",
169
"opacity-0 translate-y-2",
170
"opacity-100 translate-y-0"})
171
end
172
end
173