aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Arter <davea@mysociety.org>2020-01-29 18:45:23 +0000
committerDave Arter <davea@mysociety.org>2020-02-26 15:25:31 +0000
commit351fc540d8a2a43563cddbc1a82a3331c7f3879f (patch)
treec8bb93e9cb4347468ba684900669bebcfa87e777
parent1c58ffa769b8f381bbddda062491bddb2fdd6608 (diff)
Add manifest icon management to admin forms
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm21
-rw-r--r--perllib/FixMyStreet/App/Form/ManifestTheme.pm39
-rw-r--r--perllib/FixMyStreet/PhotoStorage.pm6
-rw-r--r--t/app/controller/admin/manifesttheme.t103
-rw-r--r--templates/web/base/admin/manifesttheme/form.html29
-rw-r--r--web/cobrands/fixmystreet/admin.js7
-rw-r--r--web/cobrands/sass/_admin.scss5
7 files changed, 199 insertions, 11 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm
index d2eed4839..66e22b483 100644
--- a/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm
@@ -39,7 +39,14 @@ sub item :PathPart('admin/manifesttheme') :Chained :CaptureArgs(1) {
sub edit :PathPart('') :Chained('item') :Args(0) {
my ($self, $c) = @_;
- return $self->form($c, $c->stash->{obj});
+
+ my $form = $self->form($c, $c->stash->{obj});
+
+ # We need to do this after form processing, in case a form POST has deleted
+ # an icon.
+ $c->forward('/offline/_stash_manifest_icons', [ $c->stash->{obj}->cobrand, 1 ]);
+
+ return $form;
}
@@ -59,6 +66,7 @@ sub form {
my ($self, $c, $theme) = @_;
if ($c->get_param('delete_theme')) {
+ $c->forward('_delete_all_manifest_icons');
$theme->delete;
$c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', 'delete' ]);
$c->response->redirect($c->uri_for($self->action_for('index')));
@@ -68,13 +76,22 @@ sub form {
my $action = $theme->in_storage ? 'edit' : 'add';
my $form = FixMyStreet::App::Form::ManifestTheme->new( cobrand => $c->cobrand->moniker );
$c->stash(template => 'admin/manifesttheme/form.html', form => $form);
- $form->process(item => $theme, params => $c->req->params);
+ my $params = $c->req->params;
+ $params->{icon} = $c->req->upload('icon') if $params->{icon};
+ $form->process(item => $theme, params => $params);
return unless $form->validated;
$c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', $action ]);
$c->response->redirect($c->uri_for($self->action_for('index')));
}
+sub _delete_all_manifest_icons :Private {
+ my ($self, $c) = @_;
+ $c->forward('/offline/_stash_manifest_icons', [ $c->stash->{obj}->cobrand, 1 ]);
+ foreach my $icon ( @{ $c->stash->{manifest_icons} } ) {
+ unlink FixMyStreet->path_to('web', $icon->{src});
+ }
+}
1;
diff --git a/perllib/FixMyStreet/App/Form/ManifestTheme.pm b/perllib/FixMyStreet/App/Form/ManifestTheme.pm
index 17e43ff58..7f3b629c0 100644
--- a/perllib/FixMyStreet/App/Form/ManifestTheme.pm
+++ b/perllib/FixMyStreet/App/Form/ManifestTheme.pm
@@ -1,5 +1,9 @@
package FixMyStreet::App::Form::ManifestTheme;
+use Path::Tiny;
+use File::Copy;
+use Digest::SHA qw(sha1_hex);
+use File::Basename;
use HTML::FormHandler::Moose;
use FixMyStreet::App::Form::I18N;
extends 'HTML::FormHandler::Model::DBIC';
@@ -15,6 +19,8 @@ has_field 'name' => ( required => 1 );
has_field 'short_name' => ( required => 1 );
has_field 'background_colour' => ( required => 0 );
has_field 'theme_colour' => ( required => 0 );
+has_field 'icon' => ( required => 0, type => 'Upload', label => "Add icon" );
+has_field 'delete_icon' => ( type => 'Multiple' );
before 'update_model' => sub {
my $self = shift;
@@ -23,6 +29,39 @@ before 'update_model' => sub {
sub _build_language_handle { FixMyStreet::App::Form::I18N->new }
+sub validate {
+ my $self = shift;
+
+ my $value = $self->value;
+ my $cobrand = $value->{cobrand} || $self->cobrand;
+ my $upload = $value->{icon};
+
+ if ( $upload ) {
+ if( $upload->type !~ /^image/ ) {
+ $self->field('icon')->add_error( _("File type not recognised. Please upload an image.") );
+ return;
+ }
+
+ my $uri = '/theme/' . $cobrand;
+ my $theme_path = path(FixMyStreet->path_to('web' . $uri));
+ $theme_path->mkpath;
+ FixMyStreet::PhotoStorage::base64_decode_upload(undef, $upload);
+ my ($p, $n, $ext) = fileparse($upload->filename, qr/\.[^.]*/);
+ my $key = sha1_hex($upload->slurp) . $ext;
+ my $out = path($theme_path, $key);
+ unless (copy($upload->tempname, $out)) {
+ $self->field('icon')->add_error( _("Sorry, we couldn't save your file(s), please try again.") );
+ return;
+ }
+ }
+
+ foreach my $delete_icon ( @{ $value->{delete_icon} } ) {
+ unlink FixMyStreet->path_to('web', $delete_icon);
+ }
+
+ return 1;
+}
+
__PACKAGE__->meta->make_immutable;
1;
diff --git a/perllib/FixMyStreet/PhotoStorage.pm b/perllib/FixMyStreet/PhotoStorage.pm
index 9b0e5c9c3..256d46361 100644
--- a/perllib/FixMyStreet/PhotoStorage.pm
+++ b/perllib/FixMyStreet/PhotoStorage.pm
@@ -58,8 +58,10 @@ sub base64_decode_upload {
print $fh $decoded;
close $fh
} else {
- $c->log->info('Couldn\'t open temp file to save base64 decoded image: ' . $!);
- $c->stash->{photo_error} = _("Sorry, we couldn't save your file(s), please try again.");
+ if ($c) {
+ $c->log->info('Couldn\'t open temp file to save base64 decoded image: ' . $!);
+ $c->stash->{photo_error} = _("Sorry, we couldn't save your file(s), please try again.");
+ }
return ();
}
}
diff --git a/t/app/controller/admin/manifesttheme.t b/t/app/controller/admin/manifesttheme.t
index ba798f64f..11802cffc 100644
--- a/t/app/controller/admin/manifesttheme.t
+++ b/t/app/controller/admin/manifesttheme.t
@@ -1,5 +1,6 @@
-use FixMyStreet::TestMech;
+use Path::Tiny;
use FixMyStreet::DB;
+use FixMyStreet::TestMech;
my $mech = FixMyStreet::TestMech->new;
@@ -72,21 +73,92 @@ subtest "cobrand admin lets you create a new theme" => sub {
is $log->action, "add";
is $log->object_summary, "lincolnshire";
is $log->link, "/admin/manifesttheme/lincolnshire";
+};
- $fields = {
+subtest "cobrand admin lets you update an existing theme" => sub {
+ $mech->get_ok("/admin/manifesttheme/lincolnshire");
+
+ my $fields = {
background_colour => "#663399",
theme_colour => "rgb(102, 51, 153)",
};
$mech->submit_form_ok( { with_fields => $fields } );
- $theme->discard_changes;
+
+ my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' });
is $theme->background_colour, "#663399";
is $theme->theme_colour, "rgb(102, 51, 153)";
- $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first;
+ my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first;
is $log->object_id, $theme->id;
is $log->action, "edit";
};
+subtest "cobrand admin lets you add an icon to an existing theme" => sub {
+ $mech->get_ok("/admin/manifesttheme/lincolnshire");
+
+ my $sample_jpeg = path(__FILE__)->parent->parent->child("sample.jpg");
+ ok $sample_jpeg->exists, "sample image $sample_jpeg exists";
+ my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg';
+
+ $mech->post( '/admin/manifesttheme/lincolnshire',
+ Content_Type => 'form-data',
+ Content => {
+ name => "Lincolnshire FixMyStreet",
+ short_name => "Lincs FMS",
+ background_colour => "#663399",
+ theme_colour => "rgb(102, 51, 153)",
+ icon => [ $sample_jpeg, undef, Content_Type => 'image/jpeg' ],
+ },
+ );
+ ok $mech->success, 'Posted request successfully';
+
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page";
+ $mech->content_contains($icon_filename);
+ $mech->content_contains("133x100");
+ my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename));
+ ok $icon_dest->exists, "Icon stored on disk";
+};
+
+subtest "cobrand admin lets you delete an icon from an existing theme" => sub {
+ my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg';
+ my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename));
+ ok $icon_dest->exists, "Icon exists on disk";
+
+ $mech->get_ok("/admin/manifesttheme/lincolnshire");
+ my $fields = {
+ delete_icon => "/theme/lincolnshire/$icon_filename",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page";
+ $mech->content_lacks($icon_filename);
+ $mech->content_lacks("133x100");
+ ok !$icon_dest->exists, "Icon removed from disk";
+};
+
+subtest "cobrand admin rejects non-images" => sub {
+ $mech->get_ok("/admin/manifesttheme/lincolnshire");
+
+ my $sample_pdf = path(__FILE__)->parent->parent->child("sample.pdf");
+ ok $sample_pdf->exists, "sample image $sample_pdf exists";
+
+ $mech->post( '/admin/manifesttheme/lincolnshire',
+ Content_Type => 'form-data',
+ Content => {
+ name => "Lincolnshire FixMyStreet",
+ short_name => "Lincs FMS",
+ background_colour => "#663399",
+ theme_colour => "rgb(102, 51, 153)",
+ icon => [ $sample_pdf, undef, Content_Type => 'application/pdf' ],
+ },
+ );
+ ok $mech->success, 'Posted request successfully';
+
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page";
+ $mech->content_lacks("90f7a64043fb458d58de1a0703a6355e2856b15e.pdf");
+ $mech->content_contains("File type not recognised. Please upload an image.");
+};
+
subtest "theme link on cobrand admin goes to edit form when theme exists" => sub {
is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" );
@@ -110,13 +182,34 @@ subtest "create page on cobrand admin redirects to edit form when theme exists"
subtest "can delete theme" => sub {
is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" );
- $mech->get_ok("/admin/manifesttheme/lincolnshire");
my $theme_id = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' })->id;
+ # Add an icon so we can test it gets deleted when the theme is deleted
+ my $sample_jpeg = path(__FILE__)->parent->parent->child("sample.jpg");
+ ok $sample_jpeg->exists, "sample image $sample_jpeg exists";
+ my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg';
+
+ $mech->post( '/admin/manifesttheme/lincolnshire',
+ Content_Type => 'form-data',
+ Content => {
+ name => "Lincolnshire FixMyStreet",
+ short_name => "Lincs FMS",
+ background_colour => "#663399",
+ theme_colour => "rgb(102, 51, 153)",
+ icon => [ $sample_jpeg, undef, Content_Type => 'image/jpeg' ],
+ },
+ );
+ ok $mech->success, 'Posted request successfully';
+
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page";
+ my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename));
+ ok $icon_dest->exists, "Icon stored on disk";
+
$mech->submit_form_ok({ button => 'delete_theme' });
is $mech->uri->path, '/admin/manifesttheme/create', "redirected to create page";
is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme deleted" );
+ ok !$icon_dest->exists, "Icon removed from disk";
my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first;
is $log->object_id, $theme_id;
diff --git a/templates/web/base/admin/manifesttheme/form.html b/templates/web/base/admin/manifesttheme/form.html
index 72a703ab2..13ed60e04 100644
--- a/templates/web/base/admin/manifesttheme/form.html
+++ b/templates/web/base/admin/manifesttheme/form.html
@@ -1,6 +1,6 @@
[% INCLUDE 'admin/header.html' title=loc('Theme') -%]
-<form method="post">
+<form method="post" enctype="multipart/form-data">
<div class="admin-hint">
<p>[% loc("The <strong>name</strong> is a string that represents the name of the web application as it is usually displayed to the user (e.g., amongst a list of other applications, or as a label for an icon).") %]</p>
</div>
@@ -25,6 +25,33 @@
[% form.field('cobrand').render | safe %]
[% END %]
+ <table>
+ <thead>
+ <tr>
+ <th>Icon</th>
+ <th>Size</th>
+ <th>Delete?</th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH icon IN manifest_icons %]
+ <tr>
+ <td><img src="[% icon.src %]" /></td>
+ <td class="icon-size">[% icon.sizes %]</td>
+ <td><input type=checkbox name=delete_icon value='[% icon.src %]' /></td>
+ </tr>
+ [% END %]
+ <tr>
+ <td colspan=3>
+ <div class="admin-hint">
+ <p>[% loc("The <strong>icons</strong> are used when the application is installed to the user's home screen. Icons must be <strong>square</strong>, with <strong>512x512</strong>px and <strong>192x192</strong>px being the most common sizes.") %]</p>
+ </div>
+ [% form.field('icon').render | safe %]
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
<p>
<input class="btn" type="submit" name="submit" value="[% loc('Save changes') %]">
</p>
diff --git a/web/cobrands/fixmystreet/admin.js b/web/cobrands/fixmystreet/admin.js
index 8210f002f..52f4dd292 100644
--- a/web/cobrands/fixmystreet/admin.js
+++ b/web/cobrands/fixmystreet/admin.js
@@ -190,5 +190,12 @@ $(function(){
$('.js-metadata-items').on('click', '.js-metadata-option-remove', function(){
$(this).parents('.js-metadata-option').remove();
});
+
+ // On the manifest theme editing page we have tickboxes for deleting individual
+ // icons - ticking one of these should grey out that row to indicate it will be
+ // deleted upon form submission.
+ $("input[name=delete_icon]").change(function() {
+ $(this).closest("tr").toggleClass("is-deleted", this.checked);
+ });
});
diff --git a/web/cobrands/sass/_admin.scss b/web/cobrands/sass/_admin.scss
index b1f914ca8..4ae019776 100644
--- a/web/cobrands/sass/_admin.scss
+++ b/web/cobrands/sass/_admin.scss
@@ -61,7 +61,10 @@ $button_bg_col: #a1a1a1; // also search bar (tables)
}
tr.is-deleted {
background-color: #ffdddd;
- td.contact-category {
+ img {
+ filter: grayscale(1);
+ }
+ td.contact-category, td.icon-size {
text-decoration: line-through;
}
}