From dd72159db0d15ba5b46cc7b3d446af827663c3ec Mon Sep 17 00:00:00 2001 From: kimd Date: Mon, 19 Apr 2010 22:24:09 +0200 Subject: [PATCH] Added buttons to the GalleryAlbum edit view so users can rotate photos by 90 deg (RFE 620). --- docs/changelog/7.x.x.txt | 3 + ...templates_default-gallery-edit-album.wgpkg | Bin 0 -> 2695 bytes lib/WebGUI/Asset/File/GalleryFile/Photo.pm | 23 ++++ lib/WebGUI/Asset/Wobject/GalleryAlbum.pm | 80 +++++++++++-- lib/WebGUI/Help/Asset_GalleryAlbum.pm | 8 ++ lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm | 27 ++++- t/Asset/File/GalleryFile/Photo/rotate.t | 109 ++++++++++++++++++ t/Asset/Wobject/GalleryAlbum/edit.t | 68 ++++++++++- t/Storage/Image.t | 36 +++++- t/supporting_collateral/rotation_test.png | Bin 0 -> 189 bytes 10 files changed, 338 insertions(+), 16 deletions(-) create mode 100644 docs/upgrades/packages-7.9.3/root_import_gallery-templates_default-gallery-edit-album.wgpkg create mode 100644 t/Asset/File/GalleryFile/Photo/rotate.t create mode 100644 t/supporting_collateral/rotation_test.png diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 1a34f0fb8..b7e4a4363 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -1,3 +1,6 @@ +7.9.3 + - added #620: Add buttons to the GalleryAlbum edit view so users can rotate photos by 90 deg + 7.9.2 - fixed #11507: Spectre Reports Wrong Workflow Count - added #11412: Additional navigation in Gallery Photo View diff --git a/docs/upgrades/packages-7.9.3/root_import_gallery-templates_default-gallery-edit-album.wgpkg b/docs/upgrades/packages-7.9.3/root_import_gallery-templates_default-gallery-edit-album.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..54dcc5bfe9206fa7fa6317965416bcb83f875d8e GIT binary patch literal 2695 zcmV;23V8J&iwFP!00000|Lt0BbJ|D}_UHW-T3^*UNd*h+U~Ejdbz&!>^0Kak?A}$J zPy-rZ5;0O~Mz-0j{qNV^nvq5_*uihP577renwjor`fa*fY_8q?Rj=1u&F0!#z20aw z>uYP-J@ z7jVLL&1Z4QG#@an*=RcBQmb{Z?S1)no@QR-fs z5tCxZw1}ji|EkS|ygdd1XgC0+`u4}|k|#kK@!TqI3{*aT+z7A#Ac-RFiy2y|KVf0W zlX=bhh02AZU|7h>0J!QVKNxTSn4LlkmZptGF1xo3QlZ{z@xZfI_8pU~7n_ zTewMdCVBEv@uZh|f#a4XN4eBRPLdOOZR=gtO>V?2caSSwQBjPir87}+<-tn!mRw~IiWdLu}8 z6eJ916GIy28yj(l$0AYs8B6lf6WwvghpE<)L}u24U;;q`!y7Sq5Cks-g!&HirU@Sd z!xG>D3xWewQWq$E=}FBKNjU@yA8c3w^*(0B$0_Qu2gj?W-{F-%hQEp*5qmw09@oef{g2y#V@GEgrfI^r&2i5qpNKTnecayC2| zuZ4h!k$lyAr9j5H+7mkZJ7PAmi6cV%2r0njZU!mdgexDGZ%)J*Ge?g?3Mh6MB>_)r zblaAZ^w23_fP$SA35_glW&mc01m_go# zOKSnsK+&6VJ&gjO@bah+x7H&wuFss$1A>%YJ0HzelV3}kyEC|r1;qH`$Dc)M^ z_CV+b@|=i3wnL`G=mWXI0CDvKAmG#&1#xGl!vlWK*m=n1L{EX-RT|sn65xHa>Gto}` z66zg4GvSHM=LAM8jEg7*p{yF5Xe~r2tAWgDd8l~K`VxY8gk4Fo68D^)WClM>(Cz4x zP-IW@Lj(0bzy_1f5Sd{%gi^r@krItQJ_0FPZ$A@(p0=S45`&$3BOZn^2mlHXumrFJ zv%ii`@||{ckdqAAkcTjYLBKZ69I($z$Na41lm;=7o1t})1tXHt^iJ0 zti!?eZ-&kxgol^i$!%ba^pq#+IUT+?1L;y)jM6bDIPk}Ch77_eikGQB3RTQ7Xl~^u zf<8rI*;%x&6eMog^{44vgD~7H6N-I;dat@IwC!e*hK#WsqXNF@DWvVtm3sq%sLszu z-iyif!cf3Njtolq#|7EN-A=ZRN{q_$!&HG`DX=di&~k=Gq5t82+pFxOs)k%z9p8G* z1t<%Ex)RK-Ho@I4wj7Xq1&HlTmVBsC&m!rRm|Axog^)qCy&=R3U~SW|A_cRH+;6ZD z;qbLX5d5&1aRP`P!S9YAf%Q?qK?#)s!qP<{^up<=@fgdfWT6Wk%5DXls!%hEiENW7 z_nkv-f5wuDkPBi@MXpfvz-d@D$1II8@LlMY)le(bq-=^)qDqMALW2Xo0RK2dl?KW}{V1q63-eL7|Mrx@T%Q56f$hJf@g!D(^o8&zkBEA)#;qYLcim zibd+1h~GsTaEz#otcoa)RNoB)g_=_Pex1b-n@-)fKRNRU@mGeUwb$Aypg_70WgGEX zBEEL}RsZdqi^3G@4b)3khozSgLK)FjH|FY|#`gAFdwb2+meb2f_^D8kxE$IS8Hx`Z(ld3%2FNnb|DTNyX##)QQYb8fOqOX0hw@yACg-Ot_Pyw& zT}-*u;3zXKw>D%4E2evDX~^p}GN637EAP2`;c&kHmIY`Qb%UF?swQKAXyUdfWW}St zuPtFBQ`V4JP6Dg)WmP!hGJsr+Zrj0d!5%ek#kL#Z@h!9&fC?1#@Oc-_1ti-ooR83F z9+>5uxc7|v${3vb|9fimMMxYH-5cX^Oa1Eta%o&ZgqeN0MF4xs#a3<%WUn}x5Q=XRXf5o!2x}VDn(gGGi z&ZR(ws2MEsb9kkZ%wv6Q>jS5{&}?E}=*b{%X`i$T3?;7xji{`u=#^TU(F zcj@Vqt+Ti{n>q0EYR2Hh|Cjy!%inkWH@BbUe@ft|1b#~3rvxez_#Yac=qms)001pk BKx6;_ literal 0 HcmV?d00001 diff --git a/lib/WebGUI/Asset/File/GalleryFile/Photo.pm b/lib/WebGUI/Asset/File/GalleryFile/Photo.pm index 9547b4784..3fa02ca54 100644 --- a/lib/WebGUI/Asset/File/GalleryFile/Photo.pm +++ b/lib/WebGUI/Asset/File/GalleryFile/Photo.pm @@ -390,6 +390,29 @@ sub processPropertiesFromFormPost { return undef; } + +#---------------------------------------------------------------------------- + +=head2 rotate ( angle ) + +Rotate the photo clockwise by the specified C (in degrees) including the +thumbnail and all resolutions. + +=cut + +sub rotate { + my $self = shift; + my $angle = shift; + my $storage = $self->getStorageLocation; + + # Rotate all files in the storage + foreach my $file (@{$storage->getFiles}) { + $storage->rotate($file, $angle); + } + # Re-create thumbnail + $self->generateThumbnail; +} + #---------------------------------------------------------------------------- =head2 setFile ( filename ) diff --git a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm index c63fdb29e..0062b309d 100644 --- a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm +++ b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm @@ -22,6 +22,7 @@ use WebGUI::International; use WebGUI::Utility; use WebGUI::HTML; use WebGUI::ProgressBar; +use WebGUI::Storage; use Archive::Any; @@ -992,6 +993,7 @@ sub www_addArchive { $var->{ form_start } = WebGUI::Form::formHeader( $session, { action => $self->getUrl('func=addArchiveSave'), + name => 'name="galleryAlbumAddArchive"', }); $var->{ form_end } = WebGUI::Form::formFooter( $session ); @@ -1257,6 +1259,43 @@ sub www_edit { $session->errorHandler->error("Couldn't demote asset '$assetId' because we couldn't instantiate it."); } } + # Rotate to the left + elsif ( grep { $_ =~ /^rotateLeft-(.{22})$/ } $form->param ) { + my $assetId = ( grep { $_ =~ /^rotateLeft-(.{22})$/ } $form->param )[0]; + $assetId =~ s/^rotateLeft-//; + my $asset = WebGUI::Asset->newByDynamicClass( $session, $assetId ); + + if ( $asset ) { + # Add revision and create a new version tag by doing so + my $newRevision = $asset->addRevision; + # Rotate photo (i.e. all attached image files) by 90° CCW + $newRevision->rotate(-90); + # Auto-commit version tag + $newRevision->requestAutoCommit; + } + else { + $session->log->error("Couldn't rotate asset '$assetId' because we couldn't instantiate it."); + } + } + # Rotate to the right + elsif ( grep { $_ =~ /^rotateRight-(.{22})$/ } $form->param ) { + my $assetId = ( grep { $_ =~ /^rotateRight-(.{22})$/ } $form->param )[0]; + $assetId =~ s/^rotateRight-//; + my $asset = WebGUI::Asset->newByDynamicClass( $session, $assetId ); + + if ( $asset ) { + # Add revision and create a new version tag by doing so + my $newRevision = $asset->addRevision; + # Rotate photo (i.e. all attached image files) by 90° CW + $newRevision->rotate(90); + # Auto-commit version tag + $newRevision->requestAutoCommit; + } + else { + $session->log->error("Couldn't rotate asset '$assetId' because we couldn't instantiate it."); + } + } + # Delete the file elsif ( grep { $_ =~ /^delete-(.{22})$/ } $form->param ) { my $assetId = ( grep { $_ =~ /^delete-(.{22})$/ } $form->param )[0]; $assetId =~ s/^delete-//; @@ -1276,6 +1315,7 @@ sub www_edit { $var->{ form_start } = WebGUI::Form::formHeader( $session, { action => $self->getParent->getUrl('func=editSave;assetId=new;class='.__PACKAGE__), + extras => 'name="galleryAlbumAdd"', }) . WebGUI::Form::hidden( $session, { name => "ownerUserId", @@ -1294,6 +1334,7 @@ sub www_edit { $var->{ form_start } = WebGUI::Form::formHeader( $session, { action => $self->getUrl('func=edit'), + extras => 'name="galleryAlbumEdit"', }) . WebGUI::Form::hidden( $session, { name => "ownerUserId", @@ -1355,23 +1396,44 @@ sub www_edit { id => "assetIdThumbnail_$file->{ assetId }", } ); - # Raw HTML here to provide proper value for the image my $promoteLabel = $i18n->get( 'Move Up', 'Icon' ); $file->{ form_promote } - = qq{} - ; + = WebGUI::Form::submit( $session, { + name => "promote-$file->{assetId}", + value => $i18n->get( 'Move Up', 'Icon' ), + class => "promote", + }); my $demoteLabel = $i18n->get( 'Move Down', 'Icon' ); $file->{ form_demote } - = qq{} - ; + = WebGUI::Form::submit( $session, { + name => "demote-$file->{assetId}", + value => $i18n->get( 'Move Down', 'Icon' ), + class => "demote", + }); my $deleteConfirm = $i18n->get( 'template delete message', 'Asset_Photo' ); - my $deleteLabel = $i18n->get( 'Delete', 'Icon' ); $file->{ form_delete } - = qq{} - ; + = WebGUI::Form::submit( $session, { + name => "delete-$file->{assetId}", + value => $i18n->get( 'Delete', 'Icon' ), + class => "delete", + extras => "onclick=\"return confirm('$deleteConfirm')\"", + }); + + $file->{ form_rotateLeft } + = WebGUI::Form::submit( $session, { + name => "rotateLeft-$file->{assetId}", + value => $i18n->get( 'rotate left' ), + class => "rotateLeft", + }); + + $file->{ form_rotateRight } + = WebGUI::Form::submit( $session, { + name => "rotateRight-$file->{assetId}", + value => $i18n->get( 'rotate right' ), + class => "rotateRight", + }); $file->{ form_synopsis } = WebGUI::Form::HTMLArea( $session, { diff --git a/lib/WebGUI/Help/Asset_GalleryAlbum.pm b/lib/WebGUI/Help/Asset_GalleryAlbum.pm index b3c854269..b8a042a20 100644 --- a/lib/WebGUI/Help/Asset_GalleryAlbum.pm +++ b/lib/WebGUI/Help/Asset_GalleryAlbum.pm @@ -331,6 +331,14 @@ our $HELP = { name => 'form_demote', description => 'helpvar form_demote', }, + { + name => 'form_rotateLeft', + description => 'helpvar form_rotateLeft', + }, + { + name => 'form_rotateRight', + description => 'helpvar form_rotateRight', + }, { name => 'form_synopsis', description => 'helpvar form_synopsis', diff --git a/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm b/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm index 662b4847d..fd784e874 100644 --- a/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm +++ b/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm @@ -23,6 +23,18 @@ our $I18N = { context => "Label for Save button", }, + 'rotate left' => { + message => "90° CCW", + lastUpdated => 1270582436, + context => "Label for rotate left button", + }, + + 'rotate right' => { + message => "90° CW", + lastUpdated => 1270582436, + context => "Label for rotate right button", + }, + 'save message' => { message => 'Album settings saved.', lastUpdated => 0, @@ -38,12 +50,11 @@ our $I18N = { lastUpdated => 0, }, - 'delete message' => { message => 'Album has been deleted. Return to Gallery', lastUpdated => 0, }, - + 'help common title' => { message => 'Gallery Album Variables (Common)', lastUpdated => 0, @@ -524,6 +535,18 @@ our $I18N = { lastUpdated => 1213631346, context => q{Description of template variable}, }, + + 'helpvar form_rotateLeft' => { + message => q{A button to rotate the photo by 90° counter clockwise.}, + lastUpdated => 1270582436, + context => q{Description of template variable}, + }, + + 'helpvar form_rotateRight' => { + message => q{A button to rotate the photo by 90° clockwise.}, + lastUpdated => 1270582436, + context => q{Description of template variable}, + }, 'helpvar form_delete' => { message => q{A button to delete the image.}, diff --git a/t/Asset/File/GalleryFile/Photo/rotate.t b/t/Asset/File/GalleryFile/Photo/rotate.t new file mode 100644 index 000000000..b6a72f9c4 --- /dev/null +++ b/t/Asset/File/GalleryFile/Photo/rotate.t @@ -0,0 +1,109 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------- +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------- + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +# The goal of this test is to confirm correct rotation of all files attached +# to a Photo asset after calling the rotate method. +# We will only do a quick check by comparing dimensions of all attached files +# after rotation. Checks on individual pixels are alreay done in testing code +# for WebGUI::Storage + +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use Test::Deep; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); + +# Name version tag and make sure it gets cleaned up +$versionTag->set({name=>"Photo rotation test"}); +addToCleanup($versionTag); + +# Create gallery and a single album +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + imageResolutions => "1024x768", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); + +# Create single photo inside the album +my $photo + = $album->addChild({ + className => "WebGUI::Asset::File::GalleryFile::Photo", + }, + undef, + undef, + { + skipAutoCommitWorkflows => 1, + }); + +# Attach image file to photo asset (setFile also makes download versions) +$photo->setFile( WebGUI::Test->getTestCollateralPath("rotation_test.png") ); +my $storage = $photo->getStorageLocation; + +# Commit all changes +$versionTag->commit; + +#---------------------------------------------------------------------------- + +plan tests => 2; + +#---------------------------------------------------------------------------- + +# Save dimensions of images +my @oldDims; +foreach my $file ( @{$storage->getFiles('showAll') } ) { + push ( @oldDims, [ $storage->getSizeInPixels($file) ] ) unless $file eq '.'; +} + +# Rotate photo (i.e. all attached images) by 90° CW +$photo->rotate(90); + +# Save new dimensions of images in reverse order +my @newDims; +foreach my $file ( @{$storage->getFiles('showAll') } ) { + push ( @newDims, [ reverse($storage->getSizeInPixels($file)) ] ) unless $file eq '.'; +} + +# Compare dimensions +cmp_deeply( \@oldDims, \@newDims, "Check if all files were rotated by 90° CW" ); + +# Rotate photo (i.e. all attached images) by 90° CCW +$photo->rotate(-90); + +# Save new dimensions of images in original order +my @newerDims; +foreach my $file ( @{$storage->getFiles('showAll') } ) { + push ( @newerDims, [ $storage->getSizeInPixels($file) ] ) unless $file eq '.'; +} + +# Compare dimensions +cmp_deeply( \@oldDims, \@newerDims, "Check if all files were rotated by 90° CCW" ); diff --git a/t/Asset/Wobject/GalleryAlbum/edit.t b/t/Asset/Wobject/GalleryAlbum/edit.t index b250ac53a..50982aa93 100644 --- a/t/Asset/Wobject/GalleryAlbum/edit.t +++ b/t/Asset/Wobject/GalleryAlbum/edit.t @@ -9,9 +9,10 @@ # http://www.plainblack.com info@plainblack.com #------------------------------------------------------------------ -# Test editing a GalleryAlbum from the web interface -# -# +# Test editing a GalleryAlbum from the web interface. Currently, it +# is tested whether... +# - users can add albums. +# - photos can be rotated. use FindBin; use strict; @@ -73,7 +74,7 @@ if ( !$mech->success ) { plan skip_all => "Cannot load URL '$baseUrl'. Will not test."; } -plan tests => 6; # Increment this number for each test you create +plan tests => 11; # Increment this number for each test you create #---------------------------------------------------------------------------- # Visitor user cannot add albums @@ -112,6 +113,65 @@ $mech->content_contains( my $album = WebGUI::Asset->newByDynamicClass( $session, $gallery->getAlbumIds->[0] ); cmp_deeply( $properties, subhashof( $album->get ), "Properties from edit form are set correctly" ); +#---------------------------------------------------------------------------- +# Photos can be rotated using the respective form buttons + +# Use album from previous test +my $album = $gallery->getFirstChild; + +# Add single photo to this album. No need to commit since auto-commit was +# enabled for the Gallery asset. +my $photo + = $album->addChild({ + className => "WebGUI::Asset::File::GalleryFile::Photo", + }); +my $photoId = $photo->getId; + +# Attach image file to photo asset (setFile also makes download versions) +$photo->setFile( WebGUI::Test->getTestCollateralPath("rotation_test.png") ); +my $storage = $photo->getStorageLocation; + +# Save dimensions of images +my @oldDims; +foreach my $file ( @{$storage->getFiles('showAll') } ) { + push ( @oldDims, [ $storage->getSizeInPixels($file) ] ) unless $file eq '.'; +} + +# Rotate photo (i.e. all attached images) by 90° CW +$mech->get_ok( $baseUrl . $album->getUrl('func=edit'), 'Request GalleryAlbum edit screen' ); +# Select the proper form +$mech->form_name( 'galleryAlbumEdit' ); +# Try to click the "rotate right" button +$mech->submit_form_ok( { + button => 'rotateRight-' . $photoId, +}, 'Request rotation of photo by 90° CW' ); + +# Save new dimensions of images in reverse order +my @newDims; +foreach my $file ( @{$storage->getFiles('showAll') } ) { + push ( @newDims, [ reverse($storage->getSizeInPixels($file)) ] ) unless $file eq '.'; +} + +# Compare dimensions +cmp_deeply( \@oldDims, \@newDims, "Check if all files were rotated by 90° CW" ); + +# Rotate photo (i.e. all attached images) by 90° CCW. No need to request the edit view since +# an updated view was returned after the last form submittal. +$mech->form_name( 'galleryAlbumEdit' ); +# Try to click the "rotate left" button +$mech->submit_form_ok( { + button => 'rotateLeft-' . $photoId, +}, 'Request rotation of photo by 90° CCW' ); + +# Save new dimensions of images in original order +my @newerDims; +foreach my $file ( @{$storage->getFiles('showAll') } ) { + push ( @newerDims, [ $storage->getSizeInPixels($file) ] ) unless $file eq '.'; +} + +# Compare dimensions +cmp_deeply( \@oldDims, \@newerDims, "Check if all files were rotated by 90° CCW" ); + #---------------------------------------------------------------------------- # getMechLogin( baseUrl, WebGUI::User, "identifier" ) # Returns a Test::WWW::Mechanize session after logging in the given user using diff --git a/t/Storage/Image.t b/t/Storage/Image.t index 4a04d5bcd..288f2de87 100644 --- a/t/Storage/Image.t +++ b/t/Storage/Image.t @@ -65,7 +65,7 @@ my $extensionTests = [ }, ]; -plan tests => 49 + scalar @{ $extensionTests }; # increment this value for each test you create +plan tests => 54 + scalar @{ $extensionTests }; # increment this value for each test you create my $session = WebGUI::Test->session; @@ -277,6 +277,40 @@ foreach my $testImage (@testImages) { $session->setting->set('maxImageSize', $origMaxImageSize ); +#################################################### +# +# rotate +# +#################################################### + +my $rotateTest = WebGUI::Storage->create( $session ); +WebGUI::Test->storagesToDelete($rotateTest); + +# Add test image to the storage +ok( $rotateTest->addFileFromFilesystem(WebGUI::Test->getTestCollateralPath("rotation_test.png")), "Can add test image to storage" ); + +# Rotate test image by 90° CW +my $file = $rotateTest->getFiles->[0]; +$rotateTest->rotate($file, 90); +# Check dimensions +cmp_bag( [$rotateTest->getSizeInPixels( $file )], [2,3], "Check size of photo after rotating 90° CW" ); +# Check pixels +my $image = Image::Magick->new; +$image->Read( $rotateTest->getPath($file) ); +is( $image->GetPixel(x=>2, y=>0), 0, "Pixel at location [2,0] should be black" ); + +# Rotate test image by 90° CCW +my $file = $rotateTest->getFiles->[0]; +$rotateTest->rotate($file, -90); +# Check dimensions +cmp_bag( [$rotateTest->getSizeInPixels( $file )], [3,2], "Check size of photo after rotating 90° CCW" ); +# Check pixels +$image = Image::Magick->new; +$image->Read( $rotateTest->getPath($file) ); +is( $image->GetPixel(x=>0, y=>0), 0, "Pixel at location [0,0] should be black" ); + + + TODO: { local $TODO = "Methods that need to be tested"; ok(0, 'resize'); diff --git a/t/supporting_collateral/rotation_test.png b/t/supporting_collateral/rotation_test.png new file mode 100644 index 0000000000000000000000000000000000000000..5861033a44ef0014c83b64e7039cd002ff67de89 GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^OhC-c0V2&hJg)&M#^NA%Cx&(BWL^R}Y)RhkE)4%c zaKYZ?lYt_f1s;*b3=G`DAk4@xYmNj^kiEpy*OmPi3y+A3j)VnQ1yD$`#5JPCIX^cy zHLrxhxhOTUBsE2$JhLQ2!QIn0AiR-J9H>ab)5S4_<2vg>Mn(n(j>8Ih|IZ08h-zh) Y3%<+xPe!G!9;k-F)78&qol`;+0KH5vbN~PV literal 0 HcmV?d00001