@@ -2,6 +2,8 @@ defmodule ComponentsGuideWeb.ColorLive do
2
2
use ComponentsGuideWeb , :live_view
3
3
alias ComponentsGuideWeb.StylingHelpers
4
4
5
+ @ update_url_delay 500
6
+
5
7
defmodule State do
6
8
defstruct color: { :lab , 50 , 100 , - 128 }
7
9
@@ -25,17 +27,19 @@ defmodule ComponentsGuideWeb.ColorLive do
25
27
% State { color: color }
26
28
end
27
29
30
+ @ lab_separator << "~" :: utf8 >>
31
+
28
32
def decode ( :lab , input ) when is_binary ( input ) do
29
- { l , << "," :: utf8 >> <> rest } = Integer . parse ( input )
30
- { a , << "," :: utf8 >> <> rest } = Integer . parse ( rest )
33
+ { l , @ lab_separator <> rest } = Integer . parse ( input )
34
+ { a , @ lab_separator <> rest } = Integer . parse ( rest )
31
35
{ b , "" } = Integer . parse ( rest )
32
36
33
37
% __MODULE__ { color: { :lab , l , a , b } }
34
38
end
35
39
36
- def encode ( % __MODULE__ { color: color } ) do
37
- { :lab , l , a , b } = color
38
- "#{ l } , #{ a } , #{ b } "
40
+ def encode ( % __MODULE__ { color: color } ) do
41
+ { :lab , l , a , b } = color
42
+ "#{ l } #{ @ lab_separator } #{ a } #{ @ lab_separator } #{ b } "
39
43
end
40
44
41
45
def set_color ( state = % __MODULE__ { } , color ) , do: % { state | color: color }
@@ -44,7 +48,8 @@ defmodule ComponentsGuideWeb.ColorLive do
44
48
def a ( % __MODULE__ { color: { :lab , _ , a , _ } } ) , do: a
45
49
def b ( % __MODULE__ { color: { :lab , _ , _ , b } } ) , do: b
46
50
47
- def css ( % __MODULE__ { color: color } ) , do: StylingHelpers . to_css ( color )
51
+ def css_srgb ( % __MODULE__ { color: color } ) , do: StylingHelpers . to_css ( color , :srgb )
52
+ def css ( % __MODULE__ { color: color } ) , do: StylingHelpers . to_css ( color , nil )
48
53
49
54
defp to_srgb ( % __MODULE__ { color: color } ) do
50
55
{ :srgb , r , g , b } = color |> StylingHelpers . convert ( :srgb ) |> StylingHelpers . clamp ( )
@@ -65,23 +70,92 @@ defmodule ComponentsGuideWeb.ColorLive do
65
70
end
66
71
end
67
72
73
+ defp interpolate ( t , { lowest , highest } ) do
74
+ ( highest - lowest ) * t + lowest
75
+ end
76
+
68
77
def render ( assigns ) do
69
78
swatch_size = 100
70
79
{ :lab , l , a , b } = assigns . state . color
71
80
72
- gradient =
73
- Styling . linear_gradient ( "150grad" , [
74
- { :lab , l * 1.5 , a * 1.2 , b * 0.8 } ,
75
- { :lab , l , a , b } ,
76
- { :lab , l * 0.5 , a * 0.8 , b * 1.2 }
77
- ] )
81
+ l_steps = 10
82
+
83
+ l_gradient =
84
+ Styling . linear_gradient (
85
+ "150grad" ,
86
+ for ( n <- 0 .. l_steps , do: { :lab , n * ( 100 / l_steps ) , a , b } )
87
+ )
88
+
89
+ l_gradient_svg =
90
+ Styling . svg_linear_gradient (
91
+ "rotate(45)" ,
92
+ for ( n <- 0 .. l_steps , do: { :lab , interpolate ( n / l_steps , { 0.0 , 100.0 } ) , a , b } )
93
+ )
94
+
95
+ a_steps = 10
96
+
97
+ a_gradient =
98
+ Styling . linear_gradient (
99
+ "150grad" ,
100
+ for ( n <- 0 .. a_steps , do: { :lab , l , interpolate ( n / a_steps , { - 127.0 , 127.0 } ) , b } )
101
+ )
102
+
103
+ a_gradient_svg =
104
+ Styling . svg_linear_gradient (
105
+ "rotate(45)" ,
106
+ for ( n <- 0 .. a_steps , do: { :lab , l , interpolate ( n / a_steps , { - 127.0 , 127.0 } ) , b } )
107
+ )
108
+
109
+ b_steps = 10
110
+
111
+ b_gradient =
112
+ Styling . linear_gradient (
113
+ "150grad" ,
114
+ for ( n <- 0 .. b_steps , do: { :lab , l , a , interpolate ( n / b_steps , { - 127.0 , 127.0 } ) } )
115
+ )
116
+
117
+ b_gradient_svg =
118
+ Styling . svg_linear_gradient (
119
+ "rotate(45)" ,
120
+ for ( n <- 0 .. b_steps , do: { :lab , l , a , interpolate ( n / b_steps , { - 127.0 , 127.0 } ) } )
121
+ )
78
122
79
123
~L"""
80
124
<article class="text-2xl max-w-lg mx-auto text-white">
81
125
<svg width="<%= swatch_size %>" height="<%= swatch_size %>" viewbox="0 0 1 1">
82
- <rect fill="<%= State.hex (@state) %>" width="1" height="1" />
126
+ <rect fill="<%= State.css_srgb (@state) %>" width="1" height="1" />
83
127
</svg>
84
- <div style="width: 100px; height: 100px; background-image: <%= gradient %>"></div>
128
+ <div class="flex">
129
+ <svg viewBox="0 0 1 1" width="100" height="100">
130
+ <defs>
131
+ <%= l_gradient_svg %>
132
+ </defs>
133
+ <rect width="1" height="1" fill="url('#myGradient')" />
134
+ <circle cx="<%= l / 100.0 %>" cy="<%= l / 100.0 %>" r="0.05" fill="white" stroke="black" stroke-width="0.01" />
135
+ </svg>
136
+ <svg viewBox="0 0 1 1" width="100" height="100">
137
+ <defs>
138
+ <%= a_gradient_svg %>
139
+ </defs>
140
+ <rect width="1" height="1" fill="url('#myGradient')" />
141
+ <circle cx="<%= (a / 127.0) / 2.0 + 0.5 %>" cy="<%= (a / 127.0) / 2.0 + 0.5 %>" r="0.05" fill="white" stroke="black" stroke-width="0.01" />
142
+ </svg>
143
+ <svg viewBox="0 0 1 1" width="100" height="100">
144
+ <defs>
145
+ <%= b_gradient_svg %>
146
+ </defs>
147
+ <rect width="1" height="1" fill="url('#myGradient')" />
148
+ <circle cx="<%= (b / 127.0) / 2.0 + 0.5 %>" cy="<%= (b / 127.0) / 2.0 + 0.5 %>" r="0.05" fill="white" stroke="black" stroke-width="0.01" />
149
+ </svg>
150
+ <!--<div style="width: 100px; height: 100px; background-image: <%= l_gradient %>"></div>-->
151
+ <!--<div style="width: 100px; height: 100px; background-image: <%= a_gradient %>"></div>-->
152
+ <div style="width: 100px; height: 100px; background-image: <%= b_gradient %>"></div>
153
+ <svg width="100" height="100">
154
+ <foreignObject>
155
+ <div style="width: 100px; height: 100px; background-image: <%= b_gradient %>"></div>
156
+ </foreignObject>
157
+ </svg>
158
+ </div>
85
159
<form phx-change="lab_changed" class="flex flex-col">
86
160
<label>
87
161
L
@@ -103,16 +177,16 @@ defmodule ComponentsGuideWeb.ColorLive do
103
177
<dt class="font-bold">Hex:
104
178
<dd><%= State.hex(@state) %>
105
179
<dt class="font-bold">CSS:
106
- <dd><pre class="text-base whitespace-pre-wrap"><code><%= State.css (@state) %></code></pre>
180
+ <dd><pre class="text-base whitespace-pre-wrap"><code><%= State.css_srgb (@state) %></code></pre>
107
181
<dt class="font-bold">Gradient CSS:
108
- <dd><pre class="text-base whitespace-pre-wrap"><code><%= gradient %></code></pre>
182
+ <dd><pre class="text-base whitespace-pre-wrap"><code><%= l_gradient %></code></pre>
109
183
</dl>
110
184
</article>
111
185
"""
112
186
end
113
187
114
188
def mount ( _params , _session , socket ) do
115
- { :ok , assign ( socket , state: % State { } ) }
189
+ { :ok , assign ( socket , state: % State { } , tref: nil ) }
116
190
end
117
191
118
192
def handle_params ( % { "definition" => definition } , _path , socket ) do
@@ -125,18 +199,37 @@ defmodule ComponentsGuideWeb.ColorLive do
125
199
{ :noreply , socket |> assign ( :state , state ) }
126
200
end
127
201
202
+ def handle_info ( :update_url , socket ) do
203
+ state = socket . assigns . state
204
+ encoded = State . encode ( state )
205
+
206
+ { :noreply ,
207
+ socket
208
+ |> assign ( :tref , nil )
209
+ |> push_patch ( to: Routes . color_path ( socket , :lab , encoded ) , replace: true ) }
210
+ end
211
+
128
212
def handle_event ( "lab_changed" , % { "l" => l , "a" => a , "b" => b } , socket ) do
129
213
l = l |> String . to_integer ( )
130
214
a = a |> String . to_integer ( )
131
215
b = b |> String . to_integer ( )
132
216
133
217
state = socket . assigns . state |> State . set_color ( { :lab , l , a , b } )
134
- encoded = State . encode ( state )
135
218
136
- # TODO: throttle for Safari’s
219
+ case socket . assigns . tref do
220
+ nil -> nil
221
+ tref -> :timer . cancel ( tref )
222
+ end
223
+
224
+ # Throttle for Safari’s
137
225
# SecurityError: Attempt to use history.replaceState() more than 100 times per 30 seconds
138
- { :noreply ,
139
- socket
140
- |> push_patch ( to: Routes . color_path ( socket , :lab , encoded ) , replace: true ) }
226
+ { :ok , tref } = :timer . send_after ( @ update_url_delay , :update_url )
227
+
228
+ {
229
+ :noreply ,
230
+ socket
231
+ |> assign ( :state , state )
232
+ |> assign ( :tref , tref )
233
+ }
141
234
end
142
235
end
0 commit comments