|
1 | 1 | defmodule CodebattleWeb.Game.ImageController do |
2 | 2 | use CodebattleWeb, :controller |
| 3 | + use Gettext, backend: CodebattleWeb.Gettext |
3 | 4 |
|
4 | 5 | alias Codebattle.Game.Context |
5 | | - |
6 | | - @fake_html_to_image Application.compile_env(:codebattle, :fake_html_to_image, false) |
| 6 | + alias Codebattle.Game.Player |
| 7 | + alias CodebattleWeb.HtmlImage |
7 | 8 |
|
8 | 9 | def show(conn, %{"game_id" => id}) do |
9 | 10 | case Context.fetch_game(id) do |
10 | 11 | {:ok, game} -> |
11 | | - {:ok, image} = |
12 | | - game |
13 | | - |> prepare_image_html() |
14 | | - |> generate_png() |
15 | | - |
16 | | - conn |
17 | | - |> put_resp_content_type("image/png") |
18 | | - |> send_resp(200, image) |
| 12 | + cache_key = "g_#{id}_#{game.state}" |
| 13 | + html = prepare_image_html(game) |
| 14 | + HtmlImage.render_image(conn, cache_key, html) |
19 | 15 |
|
20 | | - {:error, reason} -> |
21 | | - conn |
22 | | - |> put_status(:not_found) |
23 | | - |> json(%{error: inspect(reason)}) |
| 16 | + {:error, _reason} -> |
| 17 | + send_resp(conn, :ok, "") |
24 | 18 | end |
25 | 19 | end |
26 | 20 |
|
27 | 21 | defp prepare_image_html(game) do |
28 | 22 | """ |
29 | | - <html style="background-color:#dee2e6;"> |
30 | | - <center style="padding:25px;"> |
31 | | - <img src="https://codebattle.hexlet.io/assets/images/logo.svg" alt="Logo"> |
32 | | - <span>The Codebattle</span> |
33 | | - #{render_content(game)} |
34 | | - <p>Made with <span style="color: #e25555;">♥</span> by CodebattleCoreTeam</p> |
35 | | - <p>Dear frontenders, pls, make it prettier, thx</p> |
36 | | - </center> |
| 23 | + <html> |
| 24 | + <head> |
| 25 | + <meta charset="utf-8"> |
| 26 | + <style> |
| 27 | + html, body { |
| 28 | + margin: 0; |
| 29 | + padding: 0; |
| 30 | + width: 100%; |
| 31 | + height: 100%; |
| 32 | + background: #f5f7fa; |
| 33 | + font-family: 'Helvetica Neue', Arial, sans-serif; |
| 34 | + } |
| 35 | + p { |
| 36 | + margin: 0; |
| 37 | + padding: 0; |
| 38 | + } |
| 39 | + .card { |
| 40 | + width: 100%; |
| 41 | + height: 100%; |
| 42 | + background: #ffffff; |
| 43 | + border-radius: 8px; |
| 44 | + box-shadow: 0 4px 12px rgba(0,0,0,0.15); |
| 45 | + overflow: hidden; |
| 46 | + display: flex; |
| 47 | + flex-direction: column; |
| 48 | + } |
| 49 | + .header { |
| 50 | + background: #000; |
| 51 | + font-family: 'Helvetica Neue', Arial, sans-serif; |
| 52 | + color: #fff; |
| 53 | + text-align: center; |
| 54 | + } |
| 55 | + .header img { |
| 56 | + width: 100px; |
| 57 | + margin: 15px; |
| 58 | + height: auto; |
| 59 | + } |
| 60 | + .content { |
| 61 | + flex: 1; |
| 62 | + padding: 10px; |
| 63 | + color: #333; |
| 64 | + text-align: center; |
| 65 | + display: flex; |
| 66 | + flex-direction: column; |
| 67 | + justify-content: center; |
| 68 | + } |
| 69 | + .footer { |
| 70 | + text-align: center; |
| 71 | + font-size: 12px; |
| 72 | + color: #fff; |
| 73 | + padding: 8px; |
| 74 | + background: #000; |
| 75 | + } |
| 76 | + </style> |
| 77 | + </head> |
| 78 | + <body> |
| 79 | + <div class="card"> |
| 80 | + <div class="header"> |
| 81 | + <img src="#{HtmlImage.logo_url()}" alt="Logo"> |
| 82 | + </div> |
| 83 | + <div class="content"> |
| 84 | + #{render_content(game)} |
| 85 | + </div> |
| 86 | + <div class="footer"> |
| 87 | + <p> |
| 88 | + Made with |
| 89 | + <svg width="14" height="14" viewBox="0 0 24 24" style="fill:#ff5252; vertical-align:middle;"> |
| 90 | + <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 |
| 91 | + 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 |
| 92 | + 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 |
| 93 | + 6.86-8.55 11.54L12 21.35z"/> |
| 94 | + </svg> |
| 95 | + by Codebattle |
| 96 | + </p> |
| 97 | + </div> |
| 98 | + </div> |
| 99 | + </body> |
37 | 100 | </html> |
38 | 101 | """ |
39 | 102 | end |
40 | 103 |
|
41 | | - defp render_content(%{players: []}) do |
42 | | - """ |
43 | | - <p>Codebattle game</p> |
44 | | - """ |
45 | | - end |
| 104 | + defp render_content(game) do |
| 105 | + level = Gettext.gettext(CodebattleWeb.Gettext, "Level: #{game.level}") |
| 106 | + state = Gettext.gettext(CodebattleWeb.Gettext, "Game state: #{game.state}") |
46 | 107 |
|
47 | | - defp render_content(%{players: [player1]} = game) do |
48 | | - """ |
49 | | - <p>Game state: #{game.state}</p> |
50 | | - <p>Level: #{game.level}</p> |
51 | | - #{render_player(player1)} |
52 | | - """ |
53 | | - end |
| 108 | + # If you always have exactly two players: |
| 109 | + [player1, player2] = |
| 110 | + case game.players do |
| 111 | + [p1, p2] -> [p1, p2] |
| 112 | + [p1] -> [p1, %Player{}] |
| 113 | + [] -> [%Player{}, %Player{}] |
| 114 | + end |
54 | 115 |
|
55 | | - defp render_content(%{players: [player1, player2]} = game) do |
56 | 116 | """ |
57 | | - <p>Game state: #{game.state}</p> |
58 | | - <p>Level: #{game.level}</p> |
59 | | - #{render_player(player1)} |
60 | | - <span style="font-size:77px;">VS</span> |
61 | | - #{render_player(player2)} |
| 117 | + <div style="display: flex; flex-direction: column; align-items: center; gap: 20px;"> |
| 118 | + <div style=" |
| 119 | + display: grid; |
| 120 | + grid-template-columns: 1fr auto 1fr; |
| 121 | + max-width: 800px; |
| 122 | + width: 100%; |
| 123 | + margin: 0 auto; |
| 124 | + align-items: center; |
| 125 | + text-align: center; |
| 126 | + "> |
| 127 | + <!-- Player 1 --> |
| 128 | + <div style="justify-self: end;"> |
| 129 | + #{render_player(player1)} |
| 130 | + </div> |
| 131 | +
|
| 132 | + <!-- VS --> |
| 133 | + <div style="justify-self: center; font-size: 42px; margin: 20px;"> |
| 134 | + VS |
| 135 | + </div> |
| 136 | +
|
| 137 | + <!-- Player 2 --> |
| 138 | + <div style="justify-self: start;"> |
| 139 | + #{render_player(player2)} |
| 140 | + </div> |
| 141 | + </div> |
| 142 | + <p>#{state}</p> |
| 143 | + <p>#{level}</p> |
| 144 | + </div> |
62 | 145 | """ |
63 | 146 | end |
64 | 147 |
|
65 | 148 | defp render_player(player) do |
| 149 | + result = Gettext.gettext(CodebattleWeb.Gettext, "#{player.result}") |
| 150 | + |
66 | 151 | """ |
67 | | - <div style="display:inline-block"> |
68 | | - <center> |
69 | | - <img src="#{player.avatar_url}" style="width:46px; height:46px"> |
70 | | - <p>@#{player.name} (#{player.lang}) - #{player.rating}</p> |
71 | | - </center> |
| 152 | + <div> |
| 153 | + <img src="#{player.avatar_url || HtmlImage.logo_url()}" style="width:46px; height:46px;"> |
| 154 | + <p>@#{player.name}(#{player.rating}) - #{player.lang}</p> |
| 155 | + <p>#{result}</p> |
72 | 156 | </div> |
73 | 157 | """ |
74 | 158 | end |
75 | | - |
76 | | - defp generate_png(html_content) do |
77 | | - if @fake_html_to_image do |
78 | | - {:ok, html_content} |
79 | | - else |
80 | | - ChromicPDF.capture_screenshot({:html, html_content}, capture_screenshot: %{format: "png"}) |
81 | | - end |
82 | | - end |
83 | 159 | end |
0 commit comments