rt 4.2.14 (#13852)
[freeside.git] / rt / share / html / Admin / Tools / Theme.html
index 123fae2..e8b237d 100644 (file)
@@ -2,7 +2,7 @@
 %#
 %# COPYRIGHT:
 %#
-%# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+%# This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC
 %#                                          <sales@bestpractical.com>
 %#
 %# (Except where explicitly superseded by other copyright notices)
@@ -51,7 +51,7 @@
 <& /Elements/Tabs &>
 <& /Elements/ListActions, actions => \@results &>
 
-<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/NoAuth/js/farbtastic.js"></script>
+<script type="text/javascript" src="<%RT->Config->Get('WebPath')%>/static/js/farbtastic.js"></script>
 
 <div id="simple-customize">
 <div id="upload-logo">
     <label for="logo-upload"><&|/l&>Upload a new logo</&>:</label>
     <input type="file" name="logo-upload" id="logo-upload" /><br />
     <div class="gd-support">
-% if (%gd_can) {
+% if ($valid_image_types) {
     <&|/l, $valid_image_types &>Your system supports automatic color suggestions for: [_1]</&>
 % } else {
     <&|/l&>GD is disabled or not installed. You can upload an image, but you won't get automatic color suggestions.</&>
 % }
     </div>
-    <input name="reset_logo" value="Reset to default RT Logo" type="submit" />
-    <input type="submit" value="Upload" />
+    <input name="reset_logo" value="<&|/l&>Reset to default RT Logo</&>" type="submit" />
+    <input type="submit" value="<&|/l&>Upload</&>" />
   </form>
 </div>
 
 <div id="customize-theme">
-  <h2>Customize the RT theme</h2>
+  <h2><&|/l&>Customize the RT theme</&></h2>
   <ol>
     <li>
       <label for="section"><&|/l&>Select a section</&>:</label>
       <select id="section"></select>
     </li>
     <li>
-      <div class="description"><&|/l&>Select a color for the section</&>:</div>
+      <div class="description">
+        <&|/l&>Select a color for the section</&>:
+        <div id="logo-picker-hint" style="display: none;">
+          <&|/l&>You can also click on the logo above to get colors!</&>
+        </div>
+      </div>
 % if ($colors) {
 <div class="primary-colors">
 %   for (@$colors) {
 </div>
 % }
       <div id="color-picker"></div>
+      <canvas id="logo-color-picker" title="<&|/l&>Click to choose a color</&>"></canvas>
     </li>
   </ol>
 </div>
 </div>
 
 <div id="custom-css">
-  <h2>Custom CSS (Advanced)</h2>
-  
+  <h2><&|/l&>Custom CSS (Advanced)</&></h2>
+
   <form method="POST">
     <textarea rows=20 id="user_css" name="user_css" wrap="off"><% $user_css %></textarea><br />
-    <input id="try" type="button" class="button" value="Try" />
-    <input id="reset" type="reset" value="Reset" type="submit" />
-    <input name="reset_css" value="Reset to default RT Theme" type="submit" />
-    <input value="Save" type="submit" />
+    <input id="try" type="button" class="button" value="<&|/l&>Try</&>" />
+    <input id="reset" type="reset" value="<&|/l&>Reset</&>" type="submit" />
+    <input name="reset_css" value="<&|/l&>Reset to default RT Theme</&>" type="submit" />
+    <input value="<&|/l&>Save</&>" type="submit" />
   </form>
 </div>
 
 <%ONCE>
 my @sections = (
-    ['Page'         => ['body']],
-    ['Header'       => ['div#quickbar', 'body.aileron #main-navigation #app-nav > li, body.aileron #main-navigation #app-nav > li > a, #prefs-menu > li, #prefs-menu > li > a, #logo .rtname']],
+    ['Page'         => ['body', 'div#body']],
+    ['Menu bar'     => ['div#quickbar', '#main-navigation #app-nav.sf-shadow > li, #main-navigation #app-nav.sf-shadow > li > a, #prefs-menu > li, #prefs-menu > li > a, #logo .rtname']],
+    ['Title bar'    => ['div#header']],
     ['Page title'   => ['div#header h1']],
     ['Page content' => ['div#body']],
     ['Buttons'      => ['input[type="reset"], input[type="submit"], input[class="button"]']],
@@ -173,7 +180,7 @@ jQuery(function($) {
                   newcss += "; border: none;"
 
               /* Page title's text color is the selected color */
-              if (applying[name].match(/#header/))
+              if (applying[name].match(/h1/))
                   newcss = "color: " + bg;
 
               /* Nav doesn't need a background, but it wants text color */
@@ -193,7 +200,35 @@ jQuery(function($) {
       change_color($(this).css('background-color'), $(this).css('color'));
   });
 
-
+    // Setup the canvas color picker
+    $("#logo-theme-editor img").load(function() {
+        var logo      = $(this);
+        var canvas    = $("#logo-color-picker");
+        var el_canvas = canvas.get(0);
+
+        if (!el_canvas.getContext) return;
+
+        var context      = el_canvas.getContext("2d");
+        el_canvas.width  = logo.width();
+        el_canvas.height = logo.height();
+        context.drawImage(logo.get(0), 0, 0);
+
+        logo.hide().after(canvas);
+        canvas.show().click(function(ev) {
+            ev.preventDefault();
+            var R = 0,
+                G = 1,
+                B = 2,
+                A = 3;
+            var pixel = this.getContext("2d").getImageData(ev.offsetX, ev.offsetY, 1, 1).data;
+            // Farbtastic expects values in the range of 0..1
+            var rgba  = $.makeArray(pixel).map(function(v,i) { return v / 255 });
+            var wheel = $.farbtastic("#color-picker");
+            wheel.setHSL( wheel.RGBToHSL( rgba.slice(R,A) ) );
+            // XXX TODO factor in the alpha channel too
+        });
+        $('#logo-picker-hint').show();
+    });
 });
 </script>
 <%INIT>
@@ -207,14 +242,82 @@ my $text_threshold = 0.6;
 my @results;
 my $imgdata;
 
+my $colors;
+my $valid_image_types;
+if (not RT->Config->Get('DisableGD') and Convert::Color->require) {
+    require GD;
+
+    # Always find out what GD can read...
+    my %gd_can;
+    for my $type (qw(Png Jpeg Gif)) {
+        $gd_can{$type}++ if GD::Image->can("newFrom${type}Data");
+    }
+    $valid_image_types = join(", ", map { uc } sort { lc $a cmp lc $b } keys %gd_can);
+}
+
+my $analyze_img = sub {
+    return undef unless $valid_image_types;
+
+    my $imgdata = shift;
+    return undef unless $imgdata;
+
+    # ...but only analyze the image if we have data
+    my $img = GD::Image->new($imgdata);
+    unless ($img) {
+        # This has to be one damn long line because the loc() needs to be
+        # source parsed correctly.
+        push @results, loc("Automatically suggested theme colors aren't available for your image. This might be because you uploaded an image type that your installed version of GD doesn't support. Supported types are: [_1]. You can recompile libgd and GD.pm to include support for other image types.", $valid_image_types);
+        return undef;
+    }
+
+    my %colors;
+
+    my @wsamples;
+    my @hsamples;
+    if ($img->width > 200) {
+        @wsamples = map { int($img->width*($_/200)) } (0..199);
+    } else {
+        @wsamples = ( 0 .. $img->width - 1 );
+    }
+    if ($img->height > 200) {
+        @hsamples = map { int($img->height*($_/200)) } (0..199);
+    } else {
+        @hsamples = ( 0 .. $img->height - 1 );
+    }
+    for my $i (@wsamples) {
+        for my $j (@hsamples) {
+            my @color = $img->rgb( $img->getPixel($i,$j) );
+            my $hsl = Convert::Color->new('rgb:'.join(',',map { $_ / 255 } @color))->convert_to('hsl');
+            my $c = join(',',@color);
+            next if $hsl->lightness < 0.1;
+            $colors{$c} ||= { h => $hsl->hue, s => $hsl->saturation, l => $hsl->lightness, cnt => 0, c => $c};
+            $colors{$c}->{cnt}++;
+        }
+    }
+
+    for (values %colors) {
+        $_->{rank} = $_->{s} * $_->{cnt};
+    }
+    my @top5 = grep { defined and $_->{'l'} and $_->{'c'} }
+                    (sort { $b->{rank} <=> $a->{rank} } values %colors)[0..5];
+    return \@top5;
+};
+
 if (my $file_hash = _UploadedFile( 'logo-upload' )) {
-    my ($id, $msg) = RT->System->SetAttribute( Name => "UserLogo",
-                                                Description => "User-provided logo",
-                                                Content => {
-                                                    type => $file_hash->{ContentType},
-                                                    data => $file_hash->{LargeContent},
-                                                    hash => md5_hex($file_hash->{LargeContent}),
-                                                } );
+    $colors = $analyze_img->($file_hash->{LargeContent});
+
+    my $my_system = RT::System->new( $session{CurrentUser} );
+    my ( $id, $msg ) = $my_system->SetAttribute(
+        Name        => "UserLogo",
+        Description => "User-provided logo",
+        Content     => {
+            type => $file_hash->{ContentType},
+            data => $file_hash->{LargeContent},
+            hash => md5_hex($file_hash->{LargeContent}),
+            colors => $colors,
+        },
+    );
+
     push @results, loc("Unable to set UserLogo: [_1]", $msg) unless $id;
 
     $imgdata = $file_hash->{LargeContent};
@@ -227,6 +330,19 @@ else {
         my $content = $attr->Content;
         if (ref($content) eq 'HASH') {
             $imgdata = $content->{data};
+            $colors = $content->{colors};
+            unless ($colors) {
+                # No colors cached; attempt to generate them
+                $colors = $content->{colors} = $analyze_img->($content->{data});
+                if ($content->{colors}) {
+                    # Found colors; update the attribute
+                    RT->System->SetAttribute(
+                        Name => "UserLogo",
+                        Description => "User-provided logo",
+                        Content => $content,
+                    );
+                }
+            }
         }
         else {
             RT->System->DeleteAttribute('UserLogo');
@@ -256,63 +372,6 @@ if (!$user_css) {
         } @sections
     );
 }
-
-# XXX: move this to some other modules
-
-use List::MoreUtils qw(uniq);
-
-my $has_color_analyzer = eval { require Convert::Color; 1 };
-my $colors;
-my %gd_can;
-my $valid_image_types;
-
-if (not RT->Config->Get('DisableGD') and $has_color_analyzer) {
-    require GD;
-
-    # Always find out what GD can read...
-    for my $type (qw(Png Jpeg Gif)) {
-        $gd_can{$type}++ if GD::Image->can("newFrom${type}Data");
-    }
-    $valid_image_types = join(", ", map { uc } sort { lc $a cmp lc $b } keys %gd_can);
-
-    # ...but only analyze the image if we have data
-    if ($imgdata) {
-        if ( my $img = GD::Image->new($imgdata) ) {
-            $colors = analyze_img($img);
-        }
-        else {
-            # This has to be one damn long line because the loc() needs to be
-            # source parsed correctly.
-            push @results, loc("Automatically suggested theme colors aren't available for your image. This might be because you uploaded an image type that your installed version of GD doesn't support. Supported types are: [_1]. You can recompile libgd and GD.pm to include support for other image types.", $valid_image_types);
-        }
-    }
-}
-
-sub analyze_img {
-    my $img = shift;
-    my $color;
-
-    for my $i (0..$img->width-1) {
-        for my $j (0..$img->height-1) {
-            my @color = $img->rgb( $img->getPixel($i,$j) );
-            my $hsl = Convert::Color->new('rgb:'.join(',',map { $_ / 255 } @color))->convert_to('hsl');
-            my $c = join(',',@color);
-            next if $hsl->lightness < 0.1;
-            $color->{$c} ||= { h => $hsl->hue, s => $hsl->saturation, l => $hsl->lightness, cnt => 0, c => $c};
-            $color->{$c}->{cnt}++;
-        }
-    }
-
-    for (values %$color) {
-        $_->{rank} = $_->{s} * $_->{cnt};
-    }
-    my @top5 = grep { defined and $_->{'l'} and $_->{'c'} }
-                    (sort { $b->{rank} <=> $a->{rank} } values %$color)[0..5];
-    if ((scalar uniq map {$_->{rank}} @top5) == 1) {
-        $RT::Logger->info("Only one color found in logo image");
-    }
-    return \@top5;
-}
 </%INIT>
 <%ARGS>
 $user_css => ''