commit changes before switch to new zoom API
[catagits/App-IdiotBox.git] / lib / App / IdiotBox.pm
1 package App::IdiotBox;
2
3 use Web::Simple __PACKAGE__;
4 use Method::Signatures::Simple;
5 use FindBin;
6 use HTML::Zoom;
7
8 {
9   package App::IdiotBox::Announcement;
10
11   sub made_at { shift->{made_at} } 
12   sub bucket { shift->{bucket} } 
13   sub video_count { shift->{video_count} } 
14
15   package App::IdiotBox::Bucket;
16
17   sub slug { shift->{slug} }
18   sub name { shift->{name} }
19   sub video_count {
20     exists $_[0]->{video_count}
21       ? $_[0]->{video_count}
22       : $_[0]->{videos}->count
23   }
24   sub videos { shift->{videos} }
25
26   package App::IdiotBox::Video;
27
28   sub slug { shift->{slug} }
29   sub name { shift->{name} }
30   sub author { shift->{author} }
31   sub details { shift->{details} }
32   sub bucket { shift->{bucket} }
33 }
34
35 default_config(
36   template_dir => 'share/html',
37   store => 'SQLite',
38   db_file => 'var/lib/idiotbox.db',
39 );
40
41 sub BUILD {
42   my $self = shift;
43   my $store;
44   ($store = $self->config->{store}) =~ /^(\w+)$/
45     or die "Store config should be just a name, got ${store} instead";
46   my $store_class = "App::IdiotBox::Store::${store}";
47   eval "require ${store_class}; 1"
48     or die "Couldn't load ${store} store: $@";
49   $store_class->bind($self);
50 }
51   
52
53 dispatch {
54   sub (/) { $self->show_front_page },
55   subdispatch sub (/*/...) {
56     my $bucket = $self->buckets->get({ slug => $_[1] });
57     [
58       sub (/) {
59         $self->show_bucket($bucket)
60       },
61       sub (/*/) {
62         $self->show_video($bucket->videos->get({ slug => $_[1] }));
63       }
64     ]
65   }
66 };
67
68 method recent_announcements { $self->{recent_announcements} }
69
70 method buckets { $self->{buckets} }
71
72 method show_front_page {
73   my $ann = $self->recent_announcements;
74   $self->html_response(
75     front_page => [
76       '#announcement-list' => [
77         -repeat_content => {
78           repeat_for => $ann->map(sub { [
79             '.fill-bucket-name' => [
80               -replace_content => { replace_with => $_->bucket->name }
81             ],
82             '.fill-bucket-link' => [
83               -set_attribute => { name => 'href', value => $_->bucket->slug.'/' }
84             ],
85             '.fill-new-videos' => [
86               -replace_content => { replace_with => $_->video_count }
87             ],
88             '.fill-total-videos' => [
89               -replace_content => { replace_with => $_->bucket->video_count }
90             ],
91           ] })->as_stream
92         }
93       ]
94     ]
95   );
96 }
97
98 method show_bucket ($bucket) {
99   $self->html_response(bucket => [
100     '.fill-bucket-name' => [
101       -replace_content => { replace_with => $bucket->name }
102     ],
103     '#video-list' => [
104       -repeat_content => {
105         repeat_for => $bucket->videos->map(sub { [
106           '.fill-video-name' => [
107             -replace_content => { replace_with => $_->name }
108           ],
109           '.fill-video-author' => [
110             -replace_content => { replace_with => $_->author }
111           ],
112           '.fill-video-link' => [
113             -set_attribute => {
114               name => 'href', value => $_->slug.'/'
115             }
116           ],
117         ] })->as_stream
118       }
119     ]
120   ]);
121 }
122
123 method show_video ($video) {
124   $self->html_response(video => [
125     '.fill-video-name' => [
126       -replace_content => { replace_with => $video->name }
127     ],
128     '.fill-author-name' => [
129       -replace_content => { replace_with => $video->author }
130     ],
131     '.fill-bucket-link' => [
132       -set_attribute => { name => 'href', value => '../' }
133     ],
134     '.fill-bucket-name' => [
135       -replace_content => { replace_with => $video->bucket->name }
136     ],
137     '.fill-video-details' => [
138       -replace_content => { replace_with => $video->details }
139     ]
140   ]);
141 }
142
143 method html_response ($template_name, $selectors) {
144   my $io = $self->_zoom_for($template_name => $selectors)->as_readable_fh;
145   return [ 200, [ 'Content-Type' => 'text/html' ], $io ]
146 }
147
148 method _template_filename_for ($name) {
149   $self->{config}{template_dir}.'/'.$name.'.html';
150 }
151
152 method _layout_zoom {
153   $self->{layout_zoom} ||= HTML::Zoom->from_file(
154     $self->_template_filename_for('layout')
155   )
156 }
157
158 method _zoom_for ($template_name, $selectors) {
159   ($self->{zoom_for_template}{$template_name} ||= do {
160     my @body;
161     HTML::Zoom->from_file(
162                   $self->_template_filename_for($template_name)
163                 )
164               ->with_selectors(
165                   '#main-content' => [
166                     -capture_events => { into => \@body }
167                   ]
168                 )
169               ->run;
170     $self->_layout_zoom->with_selectors(
171       '#main-content' => [
172         -replace_content_events => { replace_with => \@body }
173       ]
174     )->to_zoom;
175   })->with_selectors($selectors)
176 }
177
178 1;