@@ -2,6 +2,8 @@ defmodule ComponentsGuideWeb.ColorLive do
22 use ComponentsGuideWeb , :live_view
33 alias ComponentsGuideWeb.StylingHelpers
44
5+ @ update_url_delay 500
6+
57 defmodule State do
68 defstruct color: { :lab , 50 , 100 , - 128 }
79
@@ -25,17 +27,19 @@ defmodule ComponentsGuideWeb.ColorLive do
2527 % State { color: color }
2628 end
2729
30+ @ lab_separator << "~" :: utf8 >>
31+
2832 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 )
3135 { b , "" } = Integer . parse ( rest )
3236
3337 % __MODULE__ { color: { :lab , l , a , b } }
3438 end
3539
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 } "
3943 end
4044
4145 def set_color ( state = % __MODULE__ { } , color ) , do: % { state | color: color }
@@ -44,7 +48,8 @@ defmodule ComponentsGuideWeb.ColorLive do
4448 def a ( % __MODULE__ { color: { :lab , _ , a , _ } } ) , do: a
4549 def b ( % __MODULE__ { color: { :lab , _ , _ , b } } ) , do: b
4650
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 )
4853
4954 defp to_srgb ( % __MODULE__ { color: color } ) do
5055 { :srgb , r , g , b } = color |> StylingHelpers . convert ( :srgb ) |> StylingHelpers . clamp ( )
@@ -65,23 +70,92 @@ defmodule ComponentsGuideWeb.ColorLive do
6570 end
6671 end
6772
73+ defp interpolate ( t , { lowest , highest } ) do
74+ ( highest - lowest ) * t + lowest
75+ end
76+
6877 def render ( assigns ) do
6978 swatch_size = 100
7079 { :lab , l , a , b } = assigns . state . color
7180
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+ )
78122
79123 ~L"""
80124 <article class="text-2xl max-w-lg mx-auto text-white">
81125 <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" />
83127 </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>
85159 <form phx-change="lab_changed" class="flex flex-col">
86160 <label>
87161 L
@@ -103,16 +177,16 @@ defmodule ComponentsGuideWeb.ColorLive do
103177 <dt class="font-bold">Hex:
104178 <dd><%= State.hex(@state) %>
105179 <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>
107181 <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>
109183 </dl>
110184 </article>
111185 """
112186 end
113187
114188 def mount ( _params , _session , socket ) do
115- { :ok , assign ( socket , state: % State { } ) }
189+ { :ok , assign ( socket , state: % State { } , tref: nil ) }
116190 end
117191
118192 def handle_params ( % { "definition" => definition } , _path , socket ) do
@@ -125,18 +199,37 @@ defmodule ComponentsGuideWeb.ColorLive do
125199 { :noreply , socket |> assign ( :state , state ) }
126200 end
127201
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+
128212 def handle_event ( "lab_changed" , % { "l" => l , "a" => a , "b" => b } , socket ) do
129213 l = l |> String . to_integer ( )
130214 a = a |> String . to_integer ( )
131215 b = b |> String . to_integer ( )
132216
133217 state = socket . assigns . state |> State . set_color ( { :lab , l , a , b } )
134- encoded = State . encode ( state )
135218
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
137225 # 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+ }
141234 end
142235end
0 commit comments