latin: hardcoded tetromino block cascades
[sheet.git] / writer.plp
index ad46616fb348260530eed9f5f494900b57491cc1..c6d5898b77a93870ba9e3f1fbace448b44c13e89 100644 (file)
@@ -11,6 +11,7 @@ EOT
 });
 
 use List::Util qw( pairs pairkeys );
+use Shiar_Sheet::FormRow;
 
 my $db = eval {
        require Shiar_Sheet::DB;
@@ -18,6 +19,7 @@ my $db = eval {
 } or Abort('Database error', 501, $@);
 
 my $user = eval {
+       my $rootpath = ($ENV{REQUEST_URI} // '/writer') =~ s{(?<!^)/.+}{}r;
        if (defined $post{username}) {
                $cookie{login} = EncodeURI(join ':', @post{qw( username pass )});
        }
@@ -26,7 +28,7 @@ my $user = eval {
                if (AddCookie(CGI::Cookie->new(
                        -name    => 'login',
                        -value   => '',
-                       -path    => '/writer',
+                       -path    => $rootpath,
                        -expires => 'now',
                )->as_string)) {
                        delete $cookie{login};
@@ -46,7 +48,7 @@ my $user = eval {
                my $httpcookie = CGI::Cookie->new(
                        -name    => 'login',
                        -value   => join(':', @{$found}{qw( username pass )}),
-                       -path    => '/writer',
+                       -path    => $rootpath,
                ) or die "prepared object is empty\n";
                AddCookie($httpcookie->as_string);
        } or Abort(["Unable to create login cookie", $@], 403);
@@ -71,6 +73,8 @@ my %lang = (
        en => ["\N{REGIONAL INDICATOR SYMBOL LETTER G}\N{REGIONAL INDICATOR SYMBOL LETTER B}", 'english'],
        eo => ['<span style="color:green">★</span>', 'esperanto'],
        ru => ["\N{REGIONAL INDICATOR SYMBOL LETTER R}\N{REGIONAL INDICATOR SYMBOL LETTER U}", 'русский'],
+       zh => ["\N{REGIONAL INDICATOR SYMBOL LETTER C}\N{REGIONAL INDICATOR SYMBOL LETTER N}", '中文'],
+       la => ["\N{PUSHPIN}", 'latin'],
 );
 my @wordcols = pairkeys
 my %wordcol = (
@@ -95,8 +99,13 @@ my %wordcol = (
        form    => {-label => 'Title'},
        alt     => {-label => 'Synonyms', -multiple => 1},
        wptitle => {-label => 'Wikipedia'},
-       source  => {-label => 'Image'},
-       thumb   => {-label => 'Convert options', -multiple => 1},
+       source  => {-label => 'Image', -src => sub {
+               return "data/word/org/$_[0]->{id}.jpg";
+       }},
+       thumb   => {-label => 'Convert options', -multiple => 1, -src => sub {
+               return "data/word/en/$_[0]->{id}.jpg";
+       }},
+       story   => {-label => 'Story', type => 'textarea', hidden => 'hidden'},
 );
 
 if (my $search = $fields{q}) {
@@ -105,7 +114,7 @@ if (my $search = $fields{q}) {
        say '<h1>Search</h1><ul>';
        printf("<li><small>%s</small> %s %s</li>\n",
                $_->{id}, showlink($_->{form}, "/writer/$_->{id}"),
-               sprintf('<img src="/%s" style="height:3ex; width:auto" />', Shiar_Sheet::FormRow::imagepath($_ => 'thumb')) x defined $_->{thumb}
+               sprintf('<img src="/%s" style="height:3ex; width:auto" />', $wordcol{thumb}->{-src}->($_)) x defined $_->{thumb}
        ) for $results->hashes;
        say "</ul>\n";
        exit;
@@ -179,67 +188,24 @@ elsif (defined $post{form}) {{
                return 1;
        } or Alert('Error creating translation entries', $@);
 
-       my $imgpath = Shiar_Sheet::FormRow::imagepath($row, 'source');
+       require Shiar_Sheet::ImagePrep;
+       my $image = Shiar_Sheet::ImagePrep->new($wordcol{source}->{-src}->($row));
        my $reimage = eval {
                ($row->{source} // '') ne ($replace->{source} // '') or return;
-               # copy changed remote url to local file
-               unlink $imgpath if -e $imgpath;
-               my $download = $row->{source} or return 1;
-               require LWP::UserAgent;
-               my $ua = LWP::UserAgent->new;
-               $ua->agent('/');
-               my $status = $ua->mirror($download, $imgpath);
-               $status->is_success
-                       or die "Download from <q>$download</q> failed: ".$status->status_line."\n";
+               $image->download($row->{source});
        };
        !$@ or Alert(["Source image not found", $@]);
 
        $reimage ||= $row->{thumb} ~~ $replace->{thumb};  # different convert
        $reimage ||= $row->{cover} ~~ $replace->{cover};  # resize
        $reimage++ if $fields{rethumb};  # force refresh
-
-       my $thumbpath = Shiar_Sheet::FormRow::imagepath($row => 'thumb');
        if ($reimage) {
-               if (-e $imgpath) {
-                       my $xyres = $row->{cover} ? '600x400' : '300x200';
-                       my @cmds = @{ $row->{thumb} // [] };
-                       if (my ($cmdarg) = grep { $cmds[$_] eq '-area' } 0 .. $#cmds) {
-                               # replace option by permillage crop
-                               my @dim = map { $_ / 1000 } split /\D/, $cmds[$cmdarg + 1];
-                               splice @cmds, $cmdarg, 2, (
-                                       -set => 'option:distort:viewport' => sprintf(
-                                               '%%[fx:w*%s]x%%[fx:h*%s]+%%[fx:w*%s]+%%[fx:h*%s]',
-                                               ($dim[2] || 1) - $dim[0], # width  = x2 - x1
-                                               ($dim[3] || 1) - $dim[1], # height = y2 - y1
-                                               @dim[0, 1]                # offset = x1,y1
-                                       ),
-                                       -distort => SRT => 0, # noop transform to apply viewport
-                               );
-                       }
-                       @cmds = (
-                               'convert',
-                               $imgpath,
-                               -delete => '1--1', -background => 'white',
-                               -gravity => defined $row->{thumb} ? 'northwest' : 'center',
-                               @cmds,
-                               -resize => "$xyres^", -extent => $xyres,
-                               '-strip', -quality => '60%', -interlace => 'plane',
-                               $thumbpath
-                       );
-                       eval {
-                               require IPC::Run;
-                               my $output;
-                               IPC::Run::run(\@cmds, '<' => \undef, '>&' => \$output)
-                                       or die $output ||
-                                               ($? & 127 ? "signal $?" : "error code ".($? >> 8))."\n";
-                       } or Alert([
-                               "Thumbnail image not generated",
-                               "Failed to convert source image.",
-                       ], "@cmds\n$@");
-               }
-               else {
-                       unlink $thumbpath;
-               }
+               eval {
+                       $image->convert($wordcol{thumb}->{-src}->($row), $row->{thumb});
+               } or do {
+                       my ($warn, @details) = ref $@ ? @{$@} : $@;
+                       Alert([ "Thumbnail image not generated", $warn ], @details);
+               };
        }
 }}
 else {
@@ -249,67 +215,6 @@ else {
 }
 
 my $title = $row->{id} ? "entry <small>#$row->{id}</small>" : 'new entry';
-
-package Shiar_Sheet::FormRow {
-       use PLP::Functions 'EscapeHTML';
-
-       sub input {
-               my ($row, $col, $attr) = @_;
-               my $val = $row->{$col} // '';
-               my $html = '';
-               $html .= qq( $_="$attr->{$_}") for sort grep {!/^-/} keys %{$attr // {}};
-
-               if (my $options = $attr->{-select}) {
-                       $options = $options->(@_) if ref $options eq 'CODE';
-                       $options->{$val} //= "unknown ($val)";  # preserve current
-                       return (
-                               sprintf('<select id="%s" name="%1$s">', $col),
-                               (map { sprintf('<option value="%s"%s>%s</option>',
-                                       $_, $val eq $_ && ' selected', $options->{$_}
-                               ) } sort keys %{$options}),
-                               '</select>',
-                       );
-               }
-               elsif ($attr->{type} eq 'checkbox') {
-                       $html .= ' checked' if $val;
-                       return sprintf(
-                               join('',
-                                       '<label>',
-                                       '<input name="%1$s" value="0" type="hidden" />',
-                                       '<input id="%s" name="%1$s" value="1"%s>',
-                                       ' %s</label>',
-                               ), $col, $html, $attr->{-label}
-                       );
-               }
-               else {
-                       my $multiple = ref $val eq 'ARRAY' || $attr->{-multiple};
-                       return (
-                               (map {
-                                       sprintf('<label for="%s">%s</label>', $col, $_)
-                               } $attr->{-label} // ()),
-                               $multiple ? '<span class="inline multiinput">' : (),
-                               (map {
-                                       sprintf('<input name="%s" value="%s" />', $col, EscapeHTML($_))
-                               } ref $val eq 'ARRAY' ? @{$val} : ()),
-                               sprintf('<input id="%s" name="%1$s" value="%s"%s />',
-                                       $col, $multiple ? '' : EscapeHTML($val), $html
-                               ),
-                               $multiple ? '</span>' : (),
-                               (map {
-                                       sprintf '<img id="%spreview" src="/%s" alt="%s"%s />',
-                                               $col, $_, $row->{form}, $col eq 'source' ? ' hidden' : '';
-                               } grep { -e } $row->imagepath($col)),
-                       );
-               }
-       }
-
-       sub imagepath {
-               my ($row, $col) = @_;
-               return "data/word/org/$row->{id}.jpg"   if $col eq 'source';
-               return "data/word/en/$row->{id}.jpg"  if $col eq 'thumb';
-               return;
-       }
-}
 bless $row, 'Shiar_Sheet::FormRow';
 :>
 <h1>Words <:= $title :></h1>
@@ -323,10 +228,16 @@ bless $row, 'Shiar_Sheet::FormRow';
 for my $col (@wordcols) {
        my $info = $wordcol{$col} or next;
        my ($attr, @span) = ref $info eq 'ARRAY' ? @{$info} : $info;
+       next if delete $attr->{hidden} and not $row->{$col};
        my $title = ref $attr ? delete $attr->{-label} : $attr;
        printf '<li><label for="%s">%s</label><p>', $col, $title;
                printf '<span class=inline>';
                print $row->input($col => $attr);
+               if (my $imgsrc = $attr->{-src}) {
+                       printf('<img id="%spreview" src="/%s" alt="%s"%s />',
+                               $col, $_, $row->{form}, $col eq 'source' && ' hidden'
+                       ) for grep { -e } $imgsrc->($row);
+               }
                print $row->input($_ => delete $wordcol{$_}) for @span;
                print '</span>';
        say '</p></li>';
@@ -377,7 +288,7 @@ my $parents = $db->select(word => '*', [{id => $row->{cat}}, {id => $row->{ref}}
 while (my $ref = $parents->hash) {
        printf '<li><a href="/writer/%d">%s</a></li>', $ref->{id}, Entity($ref->{form});
 }
-say "<li><strong>$row->{form}</strong></li>";
+say "<li><strong>$_</strong></li>" for Entity($row->{form});
 my $children = $db->select(word => '*', {cat => $row->{id}, ref => undef}, 'grade, id');
 while (my $ref = $children->hash) {
        printf '<li><a href="/writer/%d">%s</a></li>', $ref->{id}, Entity($ref->{form});