282 lines
9.5 kB
1
defmodule BlogWeb.EmojiSkeetsLive do
2
use BlogWeb, :live_view
3
require Logger
4
5
@emoji_options [
6
{"๐", "grinning face"},
7
{"โค๏ธ", "heart"},
8
{"๐", "thumbs up"},
9
{"๐ฅ", "fire"},
10
{"โจ", "sparkles"},
11
{"๐", "party popper"},
12
{"๐", "face with tears of joy"},
13
{"๐ค", "thinking face"},
14
{"๐", "eyes"},
15
{"๐", "rocket"},
16
{"๐", "heart eyes"},
17
{"๐", "folded hands"},
18
{"๐ฏ", "hundred points"},
19
{"๐คฃ", "rolling on the floor laughing"},
20
{"๐", "cool face with sunglasses"},
21
{"๐", "clapping hands"},
22
{"๐", "rainbow"},
23
{"๐ช", "flexed biceps"},
24
{"๐คท", "person shrugging"},
25
{"๐", "link"}
26
]
27
28
@max_skeets 10_000
29
30
def mount(_params, _session, socket) do
31
if connected?(socket) do
32
# Subscribe to the Bluesky feed
33
Phoenix.PubSub.subscribe(Blog.PubSub, "bluesky:skeet")
34
end
35
36
{:ok,
37
assign(socket,
38
page_title: "Emoji Skeet Filter",
39
meta_attrs: [
40
%{name: "title", content: "Emoji Skeet Filter"},
41
%{name: "description", content: "Filter Bluesky posts by emoji"},
42
%{property: "og:title", content: "Emoji Skeet Filter"},
43
%{property: "og:description", content: "Filter Bluesky posts by emoji"},
44
%{property: "og:type", content: "website"}
45
],
46
selected_emojis: [],
47
text_filters: [],
48
text_input: "",
49
skeets: [],
50
filtered_skeets: [],
51
emoji_options: @emoji_options
52
)}
53
end
54
55
def handle_event("toggle_emoji", %{"emoji" => emoji}, socket) do
56
selected_emojis = socket.assigns.selected_emojis
57
58
# Toggle the emoji (add if not present, remove if present)
59
updated_emojis =
60
if Enum.member?(selected_emojis, emoji) do
61
Enum.reject(selected_emojis, &(&1 == emoji))
62
else
63
[emoji | selected_emojis]
64
end
65
66
# Apply the filter with the updated emoji list
67
filtered_skeets = filter_skeets(socket.assigns.skeets, updated_emojis, socket.assigns.text_filters)
68
69
{:noreply, assign(socket, selected_emojis: updated_emojis, filtered_skeets: filtered_skeets)}
70
end
71
72
def handle_event("add_text_filter", %{"text_filter" => %{"value" => ""}}, socket) do
73
# Don't add empty filters
74
{:noreply, socket}
75
end
76
77
def handle_event("add_text_filter", %{"text_filter" => %{"value" => value}}, socket) do
78
# Add the new text filter if it's not already in the list
79
text_filters =
80
if value not in socket.assigns.text_filters do
81
[value | socket.assigns.text_filters]
82
else
83
socket.assigns.text_filters
84
end
85
86
# Apply the updated filters
87
filtered_skeets = filter_skeets(
88
socket.assigns.skeets,
89
socket.assigns.selected_emojis,
90
text_filters
91
)
92
93
{:noreply,
94
assign(socket,
95
text_filters: text_filters,
96
text_input: "",
97
filtered_skeets: filtered_skeets
98
)}
99
end
100
101
def handle_event("remove_text_filter", %{"filter" => filter}, socket) do
102
# Remove the text filter
103
text_filters = Enum.reject(socket.assigns.text_filters, &(&1 == filter))
104
105
# Apply the updated filters
106
filtered_skeets = filter_skeets(
107
socket.assigns.skeets,
108
socket.assigns.selected_emojis,
109
text_filters
110
)
111
112
{:noreply, assign(socket, text_filters: text_filters, filtered_skeets: filtered_skeets)}
113
end
114
115
def handle_event("update_text_input", %{"value" => value}, socket) do
116
{:noreply, assign(socket, text_input: value)}
117
end
118
119
def handle_info({:new_skeet, skeet}, socket) do
120
# Add the new skeet to the list, keeping only the most recent @max_skeets
121
updated_skeets =
122
[skeet | socket.assigns.skeets]
123
|> Enum.take(@max_skeets)
124
125
# Apply the current filters to the updated skeet list
126
filtered_skeets = filter_skeets(
127
updated_skeets,
128
socket.assigns.selected_emojis,
129
socket.assigns.text_filters
130
)
131
132
{:noreply, assign(socket, skeets: updated_skeets, filtered_skeets: filtered_skeets)}
133
end
134
135
defp filter_skeets(skeets, [], []) do
136
# If no emojis or text filters are selected, show no skeets
137
[]
138
end
139
140
defp filter_skeets(skeets, selected_emojis, text_filters) do
141
# Filter skeets that match both emoji and text filters
142
Enum.filter(skeets, fn skeet ->
143
emoji_match =
144
if selected_emojis == [] do
145
true # No emoji filter applied
146
else
147
Enum.any?(selected_emojis, fn emoji ->
148
String.contains?(skeet, emoji)
149
end)
150
end
151
152
text_match =
153
if text_filters == [] do
154
true # No text filter applied
155
else
156
Enum.all?(text_filters, fn filter ->
157
String.contains?(String.downcase(skeet), String.downcase(filter))
158
end)
159
end
160
161
emoji_match and text_match
162
end)
163
end
164
165
def render(assigns) do
166
~H"""
167
<div class="min-h-screen bg-gray-100 py-8">
168
<div class="max-w-4xl mx-auto px-4">
169
<h1 class="text-3xl font-bold mb-6">Emoji Skeet Filter</h1>
170
171
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
172
<h2 class="text-xl font-semibold mb-4">Select Emojis to Filter</h2>
173
<div class="flex flex-wrap gap-3">
174
<%= for {emoji, description} <- @emoji_options do %>
175
<button
176
phx-click="toggle_emoji"
177
phx-value-emoji={emoji}
178
class={"p-3 text-2xl rounded-lg transition-all #{if emoji in @selected_emojis, do: "bg-blue-100 ring-2 ring-blue-500", else: "bg-gray-100 hover:bg-gray-200"}"}
179
title={description}
180
>
181
<%= emoji %>
182
</button>
183
<% end %>
184
</div>
185
186
<div class="mt-6">
187
<h2 class="text-xl font-semibold mb-4">Add Text Filters</h2>
188
<form phx-submit="add_text_filter" class="flex gap-2">
189
<input
190
type="text"
191
name="text_filter[value]"
192
value={@text_input}
193
phx-keyup="update_text_input"
194
phx-value-value={@text_input}
195
placeholder="Enter text to filter by..."
196
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
197
/>
198
<button
199
type="submit"
200
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
201
>
202
Add Filter
203
</button>
204
</form>
205
206
<%= if @text_filters != [] do %>
207
<div class="mt-3 flex flex-wrap gap-2">
208
<%= for filter <- @text_filters do %>
209
<span class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm">
210
<%= filter %>
211
<button
212
phx-click="remove_text_filter"
213
phx-value-filter={filter}
214
class="ml-2 text-blue-500 hover:text-blue-700"
215
>
216
×
217
</button>
218
</span>
219
<% end %>
220
</div>
221
<% end %>
222
</div>
223
224
<div class="mt-4 text-sm text-gray-600">
225
<%= if @selected_emojis == [] and @text_filters == [] do %>
226
<p>No filters selected. Select at least one emoji or add a text filter to see skeets.</p>
227
<% else %>
228
<%= if @selected_emojis != [] do %>
229
<p>
230
Filtering for emojis:
231
<%= Enum.map_join(@selected_emojis, " ", fn emoji -> emoji end) %>
232
</p>
233
<% end %>
234
235
<%= if @text_filters != [] do %>
236
<p>
237
Filtering for text:
238
<%= Enum.map_join(@text_filters, ", ", & &1) %>
239
</p>
240
<% end %>
241
<% end %>
242
243
<div class="mt-2 flex justify-between text-gray-500">
244
<p>Total skeets collected: <%= length(@skeets) %></p>
245
<%= if @selected_emojis != [] or @text_filters != [] do %>
246
<p>
247
Showing <%= length(@filtered_skeets) %> of <%= length(@skeets) %> skeets
248
(<%= length(@skeets) - length(@filtered_skeets) %> filtered out)
249
</p>
250
<% end %>
251
</div>
252
</div>
253
</div>
254
255
<div class="space-y-4">
256
<%= if @filtered_skeets == [] do %>
257
<div class="bg-white rounded-lg shadow-md p-6 text-center">
258
<p class="text-gray-500">
259
<%= if @skeets == [] do %>
260
Waiting for skeets to appear...
261
<% else %>
262
<%= if @selected_emojis == [] and @text_filters == [] do %>
263
Select at least one emoji or add a text filter to see skeets.
264
<% else %>
265
No skeets match your filters. Try selecting different filters.
266
<% end %>
267
<% end %>
268
</p>
269
</div>
270
<% else %>
271
<%= for skeet <- @filtered_skeets do %>
272
<div class="bg-white rounded-lg shadow-md p-4 transition-all hover:shadow-lg">
273
<p class="text-gray-800 whitespace-pre-wrap break-words"><%= skeet %></p>
274
</div>
275
<% end %>
276
<% end %>
277
</div>
278
</div>
279
</div>
280
"""
281
end
282
end
283